@mesantosrai/pipeline-canvas
v1.0.3
Published
Pipeline canvas component library for visual workflow design
Maintainers
Readme
@mesantosrai/pipeline-canvas
A React component library for building visual pipeline/workflow canvases using React Flow.
Installation from Private GitHub Repo
Using SSH (Recommended for Private Repos)
npm install git+ssh://[email protected]:santosrai/pipeline-canvas.gitUsing HTTPS with Personal Access Token
npm install git+https://[email protected]/santosrai/pipeline-canvas.gitUsing Specific Branch or Tag
npm install git+ssh://[email protected]:santosrai/pipeline-canvas.git#mainSetup
- Install dependencies:
npm install- Build the library:
npm run buildPeer Dependencies
Make sure you have these installed in your consuming project:
npm install react react-dom reactflow zustand lucide-reactshadcn/ui Components (Required)
This library uses shadcn/ui components. You need to install the required Radix UI packages and utilities:
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-tooltip class-variance-authority clsx tailwind-mergeNote: The library includes shadcn component implementations in components/ui/, but you must install the peer dependencies above for them to work.
Usage
Basic Example (Standalone - No Dependencies)
The library works completely standalone without any dependencies:
import { PipelineCanvas, PipelineCanvasProvider } from '@mesantosrai/pipeline-canvas';
import '@mesantosrai/pipeline-canvas/style.css';
function App() {
return (
<PipelineCanvasProvider>
<div style={{ width: '100vw', height: '100vh' }}>
<PipelineCanvas />
</div>
</PipelineCanvasProvider>
);
}Note: Without dependencies, the library works in "offline mode":
- Pipelines are saved locally (localStorage)
- No backend sync
- No authentication required
- File uploads work if your API supports unauthenticated requests
With Authentication and API Client
For full functionality (backend sync, authenticated operations):
import {
PipelineCanvas,
PipelineCanvasProvider,
type ApiClient,
type AuthState
} from '@mesantosrai/pipeline-canvas';
import '@mesantosrai/pipeline-canvas/style.css';
function App() {
// Your API client (compatible with axios, fetch, or custom)
const apiClient: ApiClient = {
get: async (url: string) => {
const response = await fetch(`/api${url}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
return { data: await response.json() };
},
post: async (url: string, data: any) => {
const response = await fetch(`/api${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
return { data: await response.json() };
},
};
// Your auth state
const authState: AuthState = {
user: { id: 'user-123', email: '[email protected]' },
isAuthenticated: true,
accessToken: token,
};
// Session ID for execution context
const sessionId = 'session-456';
// Auth headers function for file uploads
const getAuthHeaders = () => ({
'Authorization': `Bearer ${token}`
});
return (
<PipelineCanvasProvider
apiClient={apiClient}
authState={authState}
sessionId={sessionId}
getAuthHeaders={getAuthHeaders}
>
<div style={{ width: '100vw', height: '100vh' }}>
<PipelineCanvas />
</div>
</PipelineCanvasProvider>
);
}With Axios API Client
If you're using axios:
import axios from 'axios';
import { PipelineCanvasProvider, type ApiClient } from '@mesantosrai/pipeline-canvas';
const apiClient: ApiClient = {
get: (url: string, config?) => axios.get(url, config),
post: (url: string, data?: any, config?) => axios.post(url, data, config),
put: (url: string, data?: any, config?) => axios.put(url, data, config),
patch: (url: string, data?: any, config?) => axios.patch(url, data, config),
delete: (url: string, config?) => axios.delete(url, config),
};
function App() {
return (
<PipelineCanvasProvider apiClient={apiClient}>
<PipelineCanvas />
</PipelineCanvasProvider>
);
}Partial Dependencies
You can provide only the dependencies you need:
// Only API client (no auth)
<PipelineCanvasProvider apiClient={myApiClient}>
<PipelineCanvas />
</PipelineCanvasProvider>
// Only auth state (local operations only)
<PipelineCanvasProvider authState={authState}>
<PipelineCanvas />
</PipelineCanvasProvider>
// Everything optional - works standalone
<PipelineCanvasProvider>
<PipelineCanvas />
</PipelineCanvasProvider>Using the Store
import { usePipelineStore } from '@mesantosrai/pipeline-canvas';
function MyComponent() {
const { nodes, edges, addNode } = usePipelineStore();
return (
<div>
<p>Nodes: {nodes.length}</p>
<button onClick={() => addNode({ id: '1', type: 'input', data: {} })}>
Add Node
</button>
</div>
);
}Using Context Directly
You can also access dependencies directly from context:
import { usePipelineContext } from '@mesantosrai/pipeline-canvas';
function MyComponent() {
const { apiClient, authState, sessionId, getAuthHeaders } = usePipelineContext();
// Use dependencies as needed
if (authState?.user) {
// User is authenticated
}
return <div>...</div>;
}Dependency Injection
The library uses dependency injection to remain standalone and flexible. All dependencies are optional:
apiClient: For backend operations (save, load, sync pipelines)authState: For user-specific features and authenticationsessionId: For execution context and session trackinggetAuthHeaders: For authenticated file uploadslogger: For structured logging (optional, uses console by default)errorReporter: For error tracking (Sentry, LogRocket, etc.)
Graceful Degradation
When dependencies are not provided:
- ✅ Pipelines work locally (localStorage)
- ✅ All UI features work
- ✅ Pipeline execution works (with provided apiClient or external APIs)
- ⚠️ Backend sync is skipped
- ⚠️ User-specific features are disabled
- ⚠️ File uploads may fail if API requires authentication
Integration Examples
With Zustand Auth Store
import { useAuthStore } from './stores/authStore';
import { PipelineCanvasProvider } from '@mesantosrai/pipeline-canvas';
function App() {
const user = useAuthStore(state => state.user);
const token = useAuthStore(state => state.accessToken);
const authState = {
user,
isAuthenticated: !!user,
accessToken: token,
};
return (
<PipelineCanvasProvider authState={authState}>
<PipelineCanvas />
</PipelineCanvasProvider>
);
}With React Context Auth
import { useContext } from 'react';
import { AuthContext } from './AuthContext';
import { PipelineCanvasProvider } from '@mesantosrai/pipeline-canvas';
function App() {
const { user, token } = useContext(AuthContext);
return (
<PipelineCanvasProvider
authState={{ user, isAuthenticated: !!user, accessToken: token }}
>
<PipelineCanvas />
</PipelineCanvasProvider>
);
}Logging and Error Tracking
The library supports structured logging and error tracking through dependency injection. This allows you to integrate with your own logging systems (Sentry, LogRocket, Bugsnag, etc.) or use the default console logger.
Basic Usage (Default Logger)
By default, the library uses a console logger that only logs in development mode:
import { PipelineCanvasProvider } from '@mesantosrai/pipeline-canvas';
// No logger needed - uses default console logger (development only)
<PipelineCanvasProvider>
<PipelineCanvas />
</PipelineCanvasProvider>Custom Logger
Provide your own logger for structured logging:
import {
PipelineCanvasProvider,
type Logger
} from '@mesantosrai/pipeline-canvas';
const myLogger: Logger = {
debug: (message, data) => {
// Your debug logging logic
console.debug(`[Pipeline] ${message}`, data);
},
info: (message, data) => {
// Your info logging logic
console.info(`[Pipeline] ${message}`, data);
},
warn: (message, data) => {
// Your warning logging logic
console.warn(`[Pipeline] ${message}`, data);
},
error: (message, error, data) => {
// Your error logging logic
console.error(`[Pipeline] ${message}`, error, data);
},
};
<PipelineCanvasProvider logger={myLogger}>
<PipelineCanvas />
</PipelineCanvasProvider>Error Tracking (Sentry Example)
Integrate with error tracking services:
import * as Sentry from '@sentry/react';
import {
PipelineCanvasProvider,
type ErrorReporter
} from '@mesantosrai/pipeline-canvas';
const errorReporter: ErrorReporter = {
captureException: (error, context) => {
Sentry.captureException(error, {
extra: context,
tags: {
component: 'pipeline-canvas',
},
});
},
captureMessage: (message, level, context) => {
Sentry.captureMessage(message, {
level: level === 'error' ? 'error' : level === 'warning' ? 'warning' : 'info',
extra: context,
tags: {
component: 'pipeline-canvas',
},
});
},
setUser: (user) => {
Sentry.setUser({
id: user.id,
email: user.email,
});
},
setContext: (key, context) => {
Sentry.setContext(key, context);
},
};
<PipelineCanvasProvider errorReporter={errorReporter}>
<PipelineCanvas />
</PipelineCanvasProvider>LogRocket Example
import LogRocket from 'logrocket';
import { PipelineCanvasProvider, type ErrorReporter } from '@mesantosrai/pipeline-canvas';
const errorReporter: ErrorReporter = {
captureException: (error, context) => {
LogRocket.captureException(error, {
extra: context,
});
},
captureMessage: (message, level, context) => {
LogRocket.captureMessage(message, {
level,
extra: context,
});
},
setUser: (user) => {
LogRocket.identify(user.id, {
email: user.email,
});
},
};
<PipelineCanvasProvider errorReporter={errorReporter}>
<PipelineCanvas />
</PipelineCanvasProvider>Using Logger in Your Code
You can access the logger from context:
import { usePipelineContext } from '@mesantosrai/pipeline-canvas';
function MyComponent() {
const { logger } = usePipelineContext();
const handleAction = () => {
logger?.info('Action performed', { action: 'click' });
};
return <button onClick={handleAction}>Click me</button>;
}What Gets Logged
The library logs:
- Pipeline execution events: Start, completion, errors
- Node execution: Status changes, errors, results
- API requests: Request/response details (if enabled)
- User actions: Pipeline save, load, delete operations
- Errors: All errors with full context (node ID, pipeline ID, error details)
Privacy and Security
- No automatic data collection: Logging is opt-in via dependency injection
- You control what gets logged: Provide your own logger/error reporter
- No external calls: Default logger only uses console (no network requests)
- Context-aware: All logs include relevant context (pipeline ID, node ID, etc.)
Theming
The library uses a scoped theming system with CSS variables prefixed with --pc- to avoid conflicts with your parent application's theme. This allows you to integrate the pipeline canvas into any app regardless of its existing styling.
Basic Setup
- Import the CSS file in your main entry:
// main.tsx or App.tsx
import '@mesantosrai/pipeline-canvas/style.css';- Configure Tailwind to include the library's classes:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/@mesantosrai/pipeline-canvas/**/*.{js,jsx,ts,tsx}',
],
theme: {
extend: {},
},
};Using the Theme Wrapper
The PipelineThemeWrapper component provides theme isolation and supports light/dark modes:
import {
PipelineCanvas,
PipelineThemeWrapper,
PipelineCanvasProvider
} from '@mesantosrai/pipeline-canvas';
// Option 1: Follow system preference (default)
<PipelineThemeWrapper>
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>
// Option 2: Force a specific theme
<PipelineThemeWrapper theme="dark">
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>
// Option 3: Sync with your app's theme state
const [appTheme, setAppTheme] = useState<'light' | 'dark'>('dark');
<PipelineThemeWrapper externalTheme={appTheme}>
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>Default White Theme (Standalone Usage)
For a default light/white theme when using the pipeline canvas in any app:
// Force light theme (default white)
<PipelineThemeWrapper theme="light">
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>
// Or rely on system preference (if system is light, it will be light)
<PipelineThemeWrapper theme="system">
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>Note: The pipeline canvas uses CSS variables at :root (light) and html.dark (dark) for portalled components (dialogs, dropdowns, etc.). For default white in any app, the :root values apply automatically. If your app sets html.dark for dark mode, portalled pipeline UI will follow that.
Portalled Components and Dark Mode
Pipeline UI components that render in portals (dialogs, dropdowns, select menus) are rendered outside .pipeline-canvas-root and rely on CSS variables at :root (light) and html.dark (dark).
For dark mode in portalled components:
Option 1: Use
externalThemeprop (recommended for apps with theme management):const { theme } = useYourAppTheme(); // Your app's theme hook <PipelineThemeWrapper externalTheme={theme}> <PipelineCanvas /> </PipelineThemeWrapper>When your app sets
html.darkfor dark mode, portalled pipeline UI will automatically use dark theme variables.Option 2: Set
html.darkclass manually when in dark mode:// In your app's theme provider useEffect(() => { if (isDark) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }, [isDark]);
For default white in any app: The :root CSS variables provide light theme values by default. If the host app never sets html.dark, portalled pipeline UI will remain light (default white).
Theme Toggle Button
Include a theme toggle button using the PipelineThemeToggle component:
import { PipelineThemeToggle, PipelineThemeWrapper } from '@mesantosrai/pipeline-canvas';
<PipelineThemeWrapper>
<div className="flex justify-end p-2">
<PipelineThemeToggle />
</div>
<PipelineCanvasProvider {...deps}>
<PipelineCanvas />
</PipelineCanvasProvider>
</PipelineThemeWrapper>Using the Theme Hook
Access the current theme programmatically:
import { usePipelineTheme, useIsDarkTheme } from '@mesantosrai/pipeline-canvas';
function MyComponent() {
const { theme, resolvedTheme, setTheme, toggleTheme } = usePipelineTheme();
const isDark = useIsDarkTheme();
return (
<div>
<p>Current theme: {resolvedTheme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}Customizing Theme Colors
Override the CSS variables in your own stylesheet to customize colors:
/* Your app's CSS file */
.pipeline-canvas-root {
/* Light theme overrides */
--pc-primary: 210 100% 50%;
--pc-canvas-bg: 0 0% 98%;
}
.pipeline-canvas-root[data-theme="dark"] {
/* Dark theme overrides */
--pc-primary: 210 100% 60%;
--pc-canvas-bg: 220 20% 10%;
}Available CSS Variables
| Variable | Description |
|----------|-------------|
| --pc-background | Main background color |
| --pc-foreground | Main text color |
| --pc-card | Card/panel background |
| --pc-primary | Primary accent color |
| --pc-secondary | Secondary color |
| --pc-muted | Muted/disabled color |
| --pc-border | Border color |
| --pc-canvas-bg | Canvas background |
| --pc-toolbar-bg | Toolbar background |
| --pc-sidebar-bg | Sidebar background |
| --pc-panel-bg | Panel background |
| --pc-text-primary | Primary text |
| --pc-text-secondary | Secondary text |
| --pc-text-muted | Muted text |
Theme Props Reference
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| theme | 'light' \| 'dark' \| 'system' | 'system' | Theme preference |
| externalTheme | 'light' \| 'dark' | - | External theme override from parent app |
| onThemeChange | (theme: 'light' \| 'dark') => void | - | Callback when theme changes |
| className | string | - | Additional CSS classes |
Development
# Type check
npm run type-check
# Build
npm run buildCreating New Node Types
When creating a new node type, please read the Node Development Guide first. It covers critical best practices and common pitfalls, including:
- ✅ Always initialize nodes with default config
- ✅ Keep execution logs accessible after completion
- ✅ Don't force navigation during execution
- ✅ Handle multiple data structures in output extraction
- ✅ Common pitfalls and how to avoid them
This guide will save you time and prevent common issues!
Project Structure
pipeline-canvas/
├── components/ # React components
│ ├── PipelineCanvas.tsx
│ ├── PipelineNodeConfig.tsx
│ ├── PipelineNodePalette.tsx
│ ├── PipelineExecution.tsx
│ ├── PipelineManager.tsx
│ ├── CustomHandle.tsx
│ ├── ExecutionLogsPanel.tsx
│ ├── PipelineThemeWrapper.tsx # Theme isolation wrapper
│ └── index.ts
├── context/ # React contexts
│ ├── PipelineContext.tsx
│ └── ThemeContext.tsx # Theme state management
├── nodes/ # Node type configurations (JSON)
│ ├── input_node/
│ ├── rfdiffusion_node/
│ ├── proteinmpnn_node/
│ └── alphafold_node/
├── store/ # Zustand store
│ └── pipelineStore.ts
├── types/ # TypeScript types
│ └── index.ts
├── utils/ # Utility functions
│ ├── topologicalSort.ts
│ └── nodeLoader.ts
├── dist/ # Build output (generated)
├── index.ts # Main export file
├── style.css # CSS styles (scoped --pc- variables)
├── package.json
├── tsconfig.json
└── vite.config.tsBackend Abstraction
The library supports backend abstraction through adapters, allowing you to integrate with any backend framework. See BACKEND_ABSTRACTION.md for:
- Adapter pattern implementation
- Custom adapter examples (FastAPI, Express, Firebase, Supabase)
- Configuration-based customization
- Migration guide
License
MIT
