Spectrum is a self-hosted workflow automation platform built with Next.js, tRPC, Prisma, AI SDK, and Inngest. It allows users to create, manage, and execute complex workflows with various triggers, actions, and AI integrations.
Editor
Node Selector
Credentials
Execution Status
Error Log
Output Log

- Visual Workflow Editor: Drag-and-drop interface built with React Flow for creating complex automation workflows
- Multiple Triggers: Support for manual triggers, webhooks (Google Forms, Stripe, PayPal)
- AI Integrations: Built-in support for OpenAI, Anthropic Claude, and Google Gemini
- Communication Actions: Send messages to Discord and Slack
- HTTP Requests: Make API calls with dynamic data and Handlebars templating
- Credential Management: Secure storage of API keys and credentials with encryption
- Execution History: Track and monitor workflow executions with detailed logs
- Real-time Updates: Live status updates during workflow execution using Inngest channels
- Authentication: Email/password and OAuth (GitHub, Google) via Better Auth
- Error Monitoring: Integrated Sentry for error tracking and debugging
- Technology Stack
- Project Structure
- Sequence Diagrams
- Database Schema
- API Endpoints
- tRPC Routers
- Core Components
- Features & Modules
- Workflow Execution
- Library Functions
- Getting Started
- Environment Variables
- Scripts
| Category | Technology | Description |
|---|---|---|
| Core | Next.js 16 | React framework with App Router and Server Components |
| TypeScript | Type-safe development | |
| Prisma | Database ORM with PostgreSQL | |
| tRPC | End-to-end typesafe API layer | |
| React 19.2 | UI library | |
| State & Data | TanStack Query | Server state management |
| Jotai | Atomic state management | |
| Zod | Schema validation | |
| SuperJSON | JSON serialization with type preservation | |
| Workflow Engine | Inngest | Durable workflow execution engine |
| @xyflow/react | Visual workflow editor | |
| Handlebars | Template engine for dynamic data | |
| AI & Integrations | @ai-sdk/openai | OpenAI integration |
| @ai-sdk/anthropic | Claude integration | |
| @ai-sdk/google | Google Gemini integration | |
| Auth & Security | Better Auth | Modern authentication solution |
| Cryptr | Encryption for sensitive credentials | |
| UI & Styling | Tailwind CSS | Utility-first CSS framework |
| Radix UI | Accessible component primitives | |
| Lucide React | Icon library | |
| Sonner | Toast notifications | |
| Developer Tools | ESLint | Code linting |
| Sentry | Error tracking | |
| mprocs | Process manager for development |
spectrum/
├── prisma/
│ ├── schema.prisma # Database schema definition
│ └── migrations/ # Database migration files
├── public/ # Static assets
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (auth)/ # Authentication pages
│ │ ├── (dashboard)/ # Main application pages
│ │ └── api/ # API routes
│ │ ├── auth/ # Authentication endpoints
│ │ ├── inngest/ # Inngest function handler
│ │ ├── trpc/ # tRPC API handler
│ │ └── webhooks/ # Webhook receivers
│ ├── components/ # React components
│ ├── config/ # Configuration files
│ ├── features/ # Feature modules
│ │ ├── auth/
│ │ ├── credentials/
│ │ ├── editor/
│ │ ├── executions/
│ │ ├── triggers/
│ │ └── workflows/
│ ├── hooks/ # Custom React hooks
│ ├── inngest/ # Workflow execution logic
│ │ ├── channels/ # Real-time status channels
│ │ ├── client.ts # Inngest client setup
│ │ ├── functions.ts # Workflow execution function
│ │ └── utils.ts # Utility functions
│ ├── lib/ # Shared utilities
│ └── trpc/ # tRPC setup
└── package.json
sequenceDiagram
actor User
participant Browser
participant NextJS as Next.js API
participant BetterAuth as Better Auth
participant DB as PostgreSQL
participant Email as Email Service
User->>Browser: Enter email/password
Browser->>NextJS: POST /api/auth/sign-up/email
NextJS->>BetterAuth: Create account
BetterAuth->>DB: Insert User, Account
DB-->>BetterAuth: User created
BetterAuth->>DB: Create Verification token
BetterAuth->>Email: Send verification email
Email-->>User: Verification email
BetterAuth-->>NextJS: User created
NextJS-->>Browser: Registration success
User->>Browser: Click verification link
Browser->>NextJS: GET /api/auth/verify-email
NextJS->>BetterAuth: Verify token
BetterAuth->>DB: Update emailVerified = true
BetterAuth-->>NextJS: Verified
NextJS-->>Browser: Redirect to dashboard
sequenceDiagram
actor User
participant Browser
participant NextJS as Next.js App
participant tRPC
participant DB as PostgreSQL
participant Inngest
participant Executor
User->>Browser: Click "New Workflow"
Browser->>tRPC: workflows.create()
tRPC->>DB: INSERT Workflow, Initial Node
DB-->>tRPC: Workflow created
tRPC-->>Browser: Workflow ID
Browser-->>User: Show editor
User->>Browser: Add nodes, configure
Browser->>tRPC: workflows.update(nodes, edges)
tRPC->>DB: BEGIN TRANSACTION
tRPC->>DB: DELETE old nodes/connections
tRPC->>DB: INSERT new nodes/connections
tRPC->>DB: COMMIT TRANSACTION
DB-->>tRPC: Updated
tRPC-->>Browser: Success
User->>Browser: Execute workflow
Browser->>tRPC: workflows.execute(id)
tRPC->>Inngest: Send event
Inngest->>Executor: executeWorkflow function
Executor->>DB: Create Execution (RUNNING)
Executor->>DB: Fetch Workflow, Nodes, Connections
Executor->>Executor: Topological sort
loop For each node
Executor->>Executor: Get executor from registry
Executor->>Executor: Execute node logic
Executor->>Executor: Update context
Executor->>Inngest: Publish status (channel)
Inngest-->>Browser: Real-time status
end
Executor->>DB: Update Execution (SUCCESS/FAILED)
Executor-->>Inngest: Execution complete
Inngest-->>Browser: Final status
Browser-->>User: Show results
sequenceDiagram
actor GoogleForms as Google Forms
participant Webhook as /api/webhooks/google-form
participant Inngest
participant Executor
participant DB as PostgreSQL
participant NextNode as Next Nodes
GoogleForms->>Webhook: POST with form data
Webhook->>Webhook: Parse workflowID from query
Webhook->>Webhook: Extract form data
Webhook->>Inngest: sendWorkflowExecution({workflowID, initialData})
Inngest-->>Webhook: Event sent
Webhook-->>GoogleForms: 200 OK
Inngest->>Executor: Trigger executeWorkflow
Executor->>DB: Create Execution record
Executor->>DB: Fetch Workflow
Executor->>Executor: Topological sort nodes
Executor->>Executor: Initialize context with form data
Executor->>NextNode: Execute first action node
NextNode->>NextNode: Access form data via context
NextNode-->>Executor: Updated context
loop Remaining nodes
Executor->>NextNode: Execute node
NextNode-->>Executor: Updated context
end
Executor->>DB: Update Execution (SUCCESS)
Executor->>DB: Save output
sequenceDiagram
participant Executor
participant HTTPNode as HTTP Request Node
participant Channel as Inngest Channel
participant Browser
participant ExternalAPI as External API
participant Context
Executor->>HTTPNode: Execute with context
HTTPNode->>Channel: Publish "loading" status
Channel-->>Browser: Real-time update
HTTPNode->>HTTPNode: Validate configuration
alt Missing configuration
HTTPNode->>Channel: Publish "error" status
HTTPNode-->>Executor: Throw NonRetriableError
end
HTTPNode->>HTTPNode: Compile Handlebars endpoint
Note over HTTPNode: {{userId}} → actual value
HTTPNode->>HTTPNode: Compile request body (if POST/PUT/PATCH)
HTTPNode->>HTTPNode: Validate JSON
HTTPNode->>ExternalAPI: HTTP Request
ExternalAPI-->>HTTPNode: Response
HTTPNode->>HTTPNode: Parse response (JSON/text)
HTTPNode->>Context: Store in context[variableName]
HTTPNode->>Channel: Publish "success" status
Channel-->>Browser: Real-time update
HTTPNode-->>Executor: Updated context
sequenceDiagram
actor User
participant Browser
participant tRPC
participant Encryption
participant DB as PostgreSQL
User->>Browser: Create credential
Browser->>Browser: Enter API key
Browser->>tRPC: credentials.create({name, type, value})
tRPC->>Encryption: encrypt(value)
Encryption-->>tRPC: Encrypted value
tRPC->>DB: INSERT Credential
DB-->>tRPC: Credential created
tRPC-->>Browser: Success
User->>Browser: Use in node
Browser->>tRPC: credentials.getByType(type)
tRPC->>DB: SELECT credentials WHERE type
DB-->>tRPC: Encrypted credentials
tRPC-->>Browser: Credential list (encrypted)
Browser->>Browser: User selects credential
Browser->>Browser: Configure node with credentialID
Note over Executor: During workflow execution
Executor->>DB: Fetch Node with credentialID
Executor->>DB: Fetch Credential
Executor->>Encryption: decrypt(value)
Encryption-->>Executor: Plain API key
Executor->>Executor: Use in AI/API call
sequenceDiagram
participant Executor
participant OpenAINode
participant Channel
participant Browser
participant DB
participant Encryption
participant OpenAI
Executor->>OpenAINode: Execute node
OpenAINode->>Channel: Publish "loading"
Channel-->>Browser: Status update
OpenAINode->>DB: Fetch credential
OpenAINode->>Encryption: decrypt(apiKey)
Encryption-->>OpenAINode: Plain API key
OpenAINode->>OpenAINode: Compile prompt with Handlebars
Note over OpenAINode: Use context variables in prompt
OpenAINode->>OpenAI: API call with prompt
OpenAI-->>OpenAINode: AI response
OpenAINode->>OpenAINode: Store in context[variableName]
OpenAINode->>Channel: Publish "success"
Channel-->>Browser: Status update
OpenAINode-->>Executor: Updated context
Purpose: Stores user account information
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key | Unique user identifier |
name |
String | Required | User's display name |
email |
String | Required, Unique | User's email address |
emailVerified |
Boolean | Default: false | Email verification status |
image |
String | Nullable | Profile picture URL |
createdAt |
DateTime | Default: now() | Account creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
sessions |
One-to-Many | Session | User authentication sessions |
accounts |
One-to-Many | Account | Connected OAuth accounts |
workflows |
One-to-Many | Workflow | User-created workflows |
credentials |
One-to-Many | Credential | Stored API credentials |
Purpose: Manages user authentication sessions
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key | Session identifier |
token |
String | Required, Unique | Session token |
expiresAt |
DateTime | Required | Expiration timestamp |
ipAddress |
String | Nullable | Client IP address |
userAgent |
String | Nullable | Client user agent string |
userId |
String | Foreign Key | Reference to User |
createdAt |
DateTime | Default: now() | Session creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
user |
Many-to-One | User | Session owner (Cascade delete) |
Purpose: Stores OAuth provider accounts and credentials
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key | Account identifier |
accountId |
String | Required | Provider-specific account ID |
providerId |
String | Required | OAuth provider identifier |
accessToken |
String | Nullable | OAuth access token |
refreshToken |
String | Nullable | OAuth refresh token |
idToken |
String | Nullable | OAuth ID token |
accessTokenExpiresAt |
DateTime | Nullable | Access token expiration |
refreshTokenExpiresAt |
DateTime | Nullable | Refresh token expiration |
scope |
String | Nullable | OAuth scope permissions |
password |
String | Nullable | Hashed password (email/password auth) |
userId |
String | Foreign Key | Reference to User |
createdAt |
DateTime | Default: now() | Account creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
user |
Many-to-One | User | Account owner (Cascade delete) |
Purpose: Handles email verification and password reset tokens
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key | Verification identifier |
identifier |
String | Required | Email or user identifier |
value |
String | Required | Verification code/token |
expiresAt |
DateTime | Required | Token expiration timestamp |
createdAt |
DateTime | Default: now() | Token creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Purpose: Represents an automation workflow
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key, CUID | Workflow identifier |
name |
String | Required | Workflow name |
userId |
String | Foreign Key | Owner reference |
createdAt |
DateTime | Default: now() | Workflow creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
user |
Many-to-One | User | Workflow owner (Cascade delete) |
nodes |
One-to-Many | Node | Workflow nodes/steps |
connections |
One-to-Many | Connection | Node connections |
executions |
One-to-Many | Execution | Execution history |
Purpose: Individual steps in a workflow
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key, CUID | Node identifier |
workflowID |
String | Foreign Key | Parent workflow reference |
name |
String | Required | Node display name |
type |
NodeType | Enum, Required | Node type |
position |
JSON | Required | Visual position {x, y} |
data |
JSON | Default: {} | Node configuration data |
credentialID |
String | Foreign Key, Nullable | Associated credential |
createdAt |
DateTime | Default: now() | Node creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
NodeType Enum Values:
| Value | Description |
|---|---|
INITIAL |
Initial/starting node |
MANUAL_TRIGGER |
Manual execution trigger |
HTTP_REQUEST |
HTTP API request |
GOOGLE_FORM_TRIGGER |
Google Forms webhook |
PAYMENT_TRIGGER |
Stripe payment webhook |
PAYPAL_TRIGGER |
PayPal webhook |
ANTHROPIC |
Claude AI integration |
GOOGLE_GEMINI |
Google Gemini AI |
OPENAI |
OpenAI integration |
DISCORD |
Discord message action |
SLACK |
Slack message action |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
workflow |
Many-to-One | Workflow | Parent workflow (Cascade delete) |
outputConnections |
One-to-Many | Connection | Outgoing connections |
inputConnections |
One-to-Many | Connection | Incoming connections |
credential |
Many-to-One | Credential | Associated credential (Optional) |
Purpose: Connects nodes to define workflow flow
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key, CUID | Connection identifier |
workflowID |
String | Foreign Key | Parent workflow reference |
fromNodeID |
String | Foreign Key | Source node reference |
toNodeID |
String | Foreign Key | Target node reference |
fromOutput |
String | Default: "main" | Output handle name |
toInput |
String | Default: "main" | Input handle name |
createdAt |
DateTime | Default: now() | Connection creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
Unique Constraint: [fromNodeID, toNodeID, fromOutput, toInput]
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
workflow |
Many-to-One | Workflow | Parent workflow (Cascade delete) |
fromNode |
Many-to-One | Node | Source node (Cascade delete) |
toNode |
Many-to-One | Node | Target node (Cascade delete) |
Purpose: Stores encrypted API keys and credentials
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key, CUID | Credential identifier |
name |
String | Required | Credential display name |
value |
String | Required | Encrypted credential value |
type |
CredentialType | Enum, Required | Credential type |
userId |
String | Foreign Key | Owner reference |
createdAt |
DateTime | Default: now() | Credential creation timestamp |
updatedAt |
DateTime | Auto-update | Last update timestamp |
CredentialType Enum Values:
| Value | Description |
|---|---|
OPENAI |
OpenAI API key |
ANTHROPIC |
Anthropic Claude API key |
GOOGLE_GEMINI |
Google Gemini API key |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
user |
Many-to-One | User | Credential owner (Cascade delete) |
Node |
One-to-Many | Node | Nodes using this credential |
Purpose: Tracks workflow execution history
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String | Primary Key, CUID | Execution identifier |
workflowID |
String | Foreign Key | Executed workflow reference |
inngestEventID |
String | Required, Unique | Inngest event ID for tracking |
status |
ExecutionStatus | Enum, Default: RUNNING | Current execution status |
startedAt |
DateTime | Default: now() | Execution start timestamp |
completedAt |
DateTime | Nullable | Execution completion timestamp |
output |
JSON | Nullable | Execution result data |
error |
String (Text) | Nullable | Error message if failed |
errorStack |
String (Text) | Nullable | Error stack trace if failed |
ExecutionStatus Enum Values:
| Value | Description |
|---|---|
RUNNING |
Execution in progress |
SUCCESS |
Execution completed successfully |
FAILED |
Execution failed with error |
Indexes:
| Index | Fields | Description |
|---|---|---|
| Primary | [workflowID, startedAt DESC] |
Optimized for execution history queries |
Relations:
| Relation | Type | Model | Description |
|---|---|---|---|
workflow |
Many-to-One | Workflow | Executed workflow (Cascade delete) |
| Endpoint | Method | File | Description |
|---|---|---|---|
/api/auth/[...all] |
GET, POST | src/app/api/auth/[...all]/route.ts | Authentication operations via Better Auth |
/api/auth/sign-in/email |
POST | (via Better Auth) | Email/password sign-in |
/api/auth/sign-up/email |
POST | (via Better Auth) | User registration |
/api/auth/session |
GET | (via Better Auth) | Get current session |
/api/auth/sign-out |
POST | (via Better Auth) | Sign out |
/api/auth/oauth/{provider} |
GET | (via Better Auth) | OAuth login (GitHub, Google) |
/api/auth/forgot-password |
POST | (via Better Auth) | Request password reset |
/api/auth/reset-password |
POST | (via Better Auth) | Reset password with token |
/api/auth/verify-email |
POST | (via Better Auth) | Verify email address |
/api/trpc/[trpc] |
GET, POST | src/app/api/trpc/[trpc]/route.ts | Main tRPC API endpoint for all procedures |
/api/inngest |
GET, POST, PUT | src/app/api/inngest/route.ts | Inngest functions for workflow execution |
/api/webhooks/google-form |
POST | src/app/api/webhooks/google-form/route.ts | Receives Google Form submissions |
/api/webhooks/stripe |
POST | src/app/api/webhooks/stripe/route.ts | Receives Stripe events |
/api/webhooks/paypal |
POST | src/app/api/webhooks/paypal/route.ts | Receives PayPal events |
All webhook endpoints require a workflowID query parameter and return:
200 OK- Event processed successfully400 Bad Request- Missing workflowID500 Internal Server Error- Processing failed
File: src/trpc/routers/_app.ts
Combines all feature routers.
Structure:
export const appRouter = createTRPCRouter({
workflows: workflowsRouter,
credentials: credentialsRouter,
executions: executionsRouter,
});File: src/features/workflows/server/router.ts
Manages workflow CRUD operations.
workflows.execute - Execute a workflow
- Input:
{ id: string } - Type: Mutation (Protected)
- Returns: Workflow object
- Functionality: Validates ownership and sends execution event to Inngest
workflows.create - Create new workflow
- Type: Mutation (Protected)
- Returns: Created workflow with initial node
- Functionality:
- Generates random slug name
- Creates workflow with INITIAL node at (0, 0)
- Associates with current user
workflows.remove - Delete workflow
- Input:
{ id: string } - Type: Mutation (Protected)
- Returns: Deleted workflow
- Functionality: Validates ownership before deletion
workflows.update - Update workflow structure
- Input:
{ id: string; nodes: Array<{ id: string; type?: string; position: { x: number; y: number }; data?: Record<string, any>; }>; edges: Array<{ source: string; target: string; sourceHandle?: string; targetHandle?: string; }>; }
- Type: Mutation (Protected)
- Returns: Updated workflow
- Functionality:
- Uses database transaction for consistency
- Deletes all existing nodes/connections
- Creates new nodes and connections
- Updates workflow timestamp
workflows.updateName - Rename workflow
- Input:
{ id: string; name: string } - Type: Mutation (Protected)
- Returns: Updated workflow
- Validation: Name must be at least 1 character
workflows.getOne - Fetch single workflow
- Input:
{ id: string } - Type: Query (Protected)
- Returns:
{ id: string; name: string; nodes: Node[]; // React Flow format edges: Edge[]; // React Flow format }
- Functionality: Transforms database format to React Flow format
workflows.getMany - List workflows with pagination
- Input:
{ page?: number; // Default: 1 pageSize?: number; // Default: 10, Range: 5-50 search?: string; // Default: "" }
- Type: Query (Protected)
- Returns:
{ items: Workflow[]; page: number; pageSize: number; totalCount: number; totalPages: number; hasNextPage: boolean; hasPrevPage: boolean; }
- Functionality:
- Filters by workflow name (case-insensitive)
- Orders by updatedAt DESC
- Includes pagination metadata
File: src/features/credentials/server/router.ts
Manages API credentials and keys.
credentials.create - Create credential
- Input:
{ name: string; type: "OPENAI" | "ANTHROPIC" | "GOOGLE_GEMINI"; value: string; }
- Type: Mutation (Protected)
- Returns: Created credential
- Functionality: Encrypts value before storage
credentials.remove - Delete credential
- Input:
{ id: string } - Type: Mutation (Protected)
- Returns: Deleted credential
credentials.update - Update credential
- Input:
{ id: string; name: string; type: "OPENAI" | "ANTHROPIC" | "GOOGLE_GEMINI"; value: string; }
- Type: Mutation (Protected)
- Returns: Updated credential
- Functionality: Re-encrypts new value
credentials.getOne - Fetch single credential
- Input:
{ id: string } - Type: Query (Protected)
- Returns: Credential (with encrypted value)
credentials.getMany - List credentials with pagination
- Input:
{ page?: number; pageSize?: number; search?: string; // Searches name field }
- Type: Query (Protected)
- Returns: Paginated credential list
- Functionality: Case-insensitive name search
credentials.getByType - Get credentials by type
- Input:
{ type: "OPENAI" | "ANTHROPIC" | "GOOGLE_GEMINI" } - Type: Query (Protected)
- Returns: Array of matching credentials
- Functionality: Orders by updatedAt DESC
File: src/features/executions/server/router.ts
Provides execution history and details.
executions.getOne - Fetch single execution
- Input:
{ id: string } - Type: Query (Protected)
- Returns: Execution with workflow details
- Includes:
workflow: { id, name }
executions.getMany - List executions with pagination
- Input:
{ page?: number; pageSize?: number; }
- Type: Query (Protected)
- Returns: Paginated execution list
- Functionality:
- Orders by startedAt DESC
- Includes workflow name and ID
- Only shows user's workflow executions
File: src/components/app-header.tsx
Purpose: Top navigation bar with sidebar toggle.
Features:
- Sidebar trigger button
- Fixed height (h-14)
- Bottom border for visual separation
File: src/components/app-sidebar.tsx
Purpose: Main navigation sidebar.
Menu Items:
- Workflows (
/workflows) - Manage automation workflows - Credentials (
/credentials) - Store API keys - Executions (
/executions) - View execution history
Features:
- Collapsible to icon-only mode
- Active route highlighting
- User profile in footer
- Sign-out functionality
- Spectrum branding with logo
State:
- Uses
usePathname()for active route detection - Client component for interactivity
File: src/components/workflow-node.tsx
Purpose: Wrapper component for React Flow nodes.
Props:
{
children: ReactNode;
showToolbar?: boolean; // Default: true
onDelete?: () => void;
onSettings?: () => void;
name?: string; // Node display name
description?: string; // Node description
}Features:
- Top Toolbar (when
showToolbar=true):- Settings button (gear icon)
- Delete button (trash icon)
- Bottom Toolbar (when
nameprovided):- Node name display
- Description (expandable on hover)
- Consistent styling across all node types
File: src/components/initial-node.tsx
Purpose: Starting point of every workflow.
Characteristics:
- Non-deletable
- No settings
- Minimal configuration
- Serves as workflow entry point
File: src/components/node-selector.tsx
Purpose: Node palette for adding nodes to workflow.
Features:
- Categorized node list
- Search/filter functionality
- Drag-and-drop to canvas
- Node type descriptions
File: src/components/entity-components.tsx
Purpose: Reusable components for entity management (workflows, credentials, executions).
Includes:
- List views
- Table components
- Action buttons
- Status badges
Directory: src/features/editor/
Purpose: Visual workflow editor implementation.
Key Files:
store/atoms.ts- Jotai atoms for editor state- Component-level state management
- Canvas interactions
- Node positioning
Directory: src/features/workflows/
Components:
params.ts- URL parameter definitionshooks/use-workflows.ts- Workflow CRUD hookshooks/use-workflows-params.ts- URL parameter hooksserver/router.ts- tRPC router (documented above)server/params-loader.ts- Server-side parameter loadingserver/prefetch.ts- Data prefetching utilities
Directory: src/features/credentials/
Purpose: Secure credential storage and management.
Features:
- Encrypted storage
- Type-based organization
- CRUD operations
- Credential selection for nodes
Directory: src/features/executions/
Purpose: Workflow execution engine and monitoring.
Key Files:
File: src/features/executions/lib/executor-registry.ts
Purpose: Maps node types to executor functions.
Registry:
{
INITIAL: manualTriggerExecutor,
MANUAL_TRIGGER: manualTriggerExecutor,
HTTP_REQUEST: httpRequestExecutor,
GOOGLE_FORM_TRIGGER: googleFromTriggerExecutor,
PAYMENT_TRIGGER: paymentTriggerExecutor,
PAYPAL_TRIGGER: paypalTriggerExecutor,
GOOGLE_GEMINI: googleGeminiExecutor,
ANTHROPIC: anthropicExecutor,
OPENAI: OpenAIExecutor,
DISCORD: DiscordExecutor,
SLACK: SlackExecutor,
}Function: getExecutor(type: NodeType): NodeExecutor
- Retrieves executor for given node type
- Throws error if executor not found
File: src/config/node-coponents.ts
Purpose: Maps node types to React components.
Components:
INITIAL→ InitialNodeHTTP_REQUEST→ HttpRequestNodeMANUAL_TRIGGER→ ManualTriggerNodeGOOGLE_FORM_TRIGGER→ GoogleFormTriggerNodePAYMENT_TRIGGER→ PaymentTriggerNodePAYPAL_TRIGGER→ PayPalTriggerNodeGOOGLE_GEMINI→ GoogleGeminiNodeOPENAI→ OpenAINodeANTHROPIC→ AnthropicNodeDISCORD→ DiscordNodeSLACK→ SlackNode
File: src/features/executions/components/http-request/executor.ts
Purpose: Makes HTTP requests with dynamic data.
Configuration:
{
variableName: string; // Context variable name for response
endpoint: string; // URL with Handlebars templates
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
body?: string; // JSON body with Handlebars templates
}Features:
- Handlebars Templating: Dynamic endpoint and body
- Example:
https://api.example.com/users/{{userId}}
- Example:
- Context Variables: Store response in context
- JSON Validation: Prevents invalid JSON in POST/PUT/PATCH
- Content Type Detection: Handles JSON and text responses
- Real-time Status: Publishes loading/error states via channels
Response Format:
{
[variableName]: {
httpResponse: {
status: number;
statusText: string;
data: any; // JSON or text
}
}
}Custom Handlebars Helper:
{{JSON context}}- Stringifies objects for templates
Directory: src/features/triggers/
Purpose: Workflow trigger implementations.
Trigger Types:
- User-initiated execution
- No external dependencies
- Used by INITIAL node
- Receives form submissions via webhook
- Parses form responses
- Extracts respondent email
- Receives Stripe events
- Handles payment lifecycle
- Supports all Stripe event types
- Receives PayPal webhooks
- Handles transaction events
- Supports PayPal event types
Common Pattern: Each trigger has:
executor.ts- Execution logicactions.ts- Node configuration UInode.tsx- React Flow node componentutils.ts(optional) - Helper functions
File: src/inngest/functions.ts
Event: workflows/execute.workflow
Purpose: Orchestrates workflow execution with fault tolerance.
Configuration:
- Retries: 1 (production), 0 (development)
- Failure Handler: Updates execution status to FAILED
Execution Flow:
-
Create Execution Record
- Step:
create-execution-history - Creates database record with RUNNING status
- Stores Inngest event ID for tracking
- Step:
-
Prepare Workflow
- Step:
prepare-workflow - Fetches workflow with nodes and connections
- Performs topological sort for execution order
- Detects cycles (throws NonRetriableError)
- Step:
-
Find User ID
- Step:
find-user-id - Retrieves workflow owner
- Used for credential access
- Step:
-
Execute Nodes
- Iterates through sorted nodes
- Gets executor from registry
- Passes context between nodes
- Each node can modify context
-
Update Execution Record
- Step:
update-execution-history - Sets status to SUCCESS
- Records completion time
- Stores final output
- Step:
Context Flow:
- Initialized with
initialDatafrom trigger - Each node receives current context
- Each node returns updated context
- Final context stored as execution output
Real-time Channels:
- HTTP Request status
- Manual Trigger status
- Google Form Trigger status
- Payment Trigger status
- PayPal Trigger status
- AI model execution status (OpenAI, Anthropic, Gemini)
- Communication status (Discord, Slack)
File: src/inngest/client.ts
Configuration:
{
id: "spectrum",
middleware: [realtimeMiddleware()],
eventKey: process.env.INNGEST_EVENT_KEY
}Purpose:
- Send workflow execution events
- Real-time status updates
- Durable execution guarantees
File: src/inngest/utils.ts
Purpose: Determines node execution order.
Parameters:
nodes: Node[]- All workflow nodesconnections: Connection[]- Node connections
Returns: Node[] - Sorted nodes
Algorithm:
- Convert connections to edges:
[fromNodeID, toNodeID] - Add self-edges for disconnected nodes
- Run toposort algorithm
- Remove duplicate IDs
- Map IDs back to node objects
Error Handling:
- Detects cycles →
NonRetriableError - Ensures valid execution order
Purpose: Triggers workflow execution.
Parameters:
{
workflowID: string;
initialData?: any;
[key: string]: any;
}Returns: Inngest event send promise
Functionality:
- Sends
workflows/execute.workflowevent - Generates unique CUID event ID
- Passes initial data to workflow context
Directory: src/inngest/channels/
Purpose: Real-time status updates during execution.
Channel Files:
http-request.ts- HTTP request progressmanual-trigger.ts- Manual execution statusgoogle-form-trigger.ts- Form submission processingpayment-trigger.ts- Payment event handlingpaypal-trigger.ts- PayPal event handlinggoogle-gemini.ts- Gemini AI executionopenai.ts- OpenAI executionanthropic.ts- Claude executiondiscord.ts- Discord message sendingslack.ts- Slack message sending
Common Status Values:
loading- In progresssuccess- Completed successfullyerror- Execution failed
Authentication - src/lib/auth.ts
Purpose: Configure Better Auth for user authentication and session management
| Configuration | Description |
|---|---|
database |
Uses Prisma adapter with PostgreSQL |
emailAndPassword.enabled |
Enable email/password authentication |
emailAndPassword.autoSignIn |
Automatically sign in after registration |
emailAndPassword.requireEmailVerification |
Require email verification before access |
emailVerification.sendOnSignUp |
Send verification email on registration |
socialProviders.github |
GitHub OAuth integration |
socialProviders.google |
Google OAuth integration |
user.deleteUser.enabled |
Enable account deletion with verification |
| Function/Feature | Description |
|---|---|
sendResetPassword |
Sends password reset email with verification link |
sendVerificationEmail |
Sends email verification link to new users |
sendDeleteAccountVerification |
Sends confirmation email for account deletion |
mapProfileToUser (GitHub) |
Maps GitHub profile data to user schema |
mapProfileToUser (Google) |
Maps Google profile data to user schema |
Database Client - src/lib/db.ts
Purpose: Provide singleton Prisma client instance for database operations
| Export | Type | Description |
|---|---|---|
prisma |
PrismaClient | Singleton Prisma client instance |
| Feature | Description |
|---|---|
| Global instance | Prevents multiple Prisma clients in development |
| Hot module reload safe | Preserves client across Next.js hot reloads |
| Production ready | Creates new instance only when needed |
Encryption - src/lib/encryption.ts
Purpose: Encrypt and decrypt sensitive credentials using AES-256
| Function | Parameters | Returns | Description |
|---|---|---|---|
encrypt |
text: string |
string |
Encrypts plain text using AES-256 via Cryptr |
decrypt |
text: string |
string |
Decrypts encrypted text back to plain text |
| Configuration | Description |
|---|---|
| Algorithm | AES-256 encryption via Cryptr library |
| Encryption Key | process.env.ENCRYPTION_KEY |
Authentication Client - src/lib/auth-client.ts
Purpose: Provide client-side authentication utilities and hooks
| Export | Type | Description |
|---|---|---|
authClient |
BetterAuthClient | Better Auth client instance for browser |
| Feature | Description |
|---|---|
| Session management hooks | React hooks for accessing current session |
| OAuth redirect handlers | Handle OAuth callback and redirect logic |
| Client-side auth state | Manage authentication state in browser |
Email Service - src/lib/email.ts
Purpose: Send transactional emails via Nodemailer
| Function | Parameters | Returns | Description |
|---|---|---|---|
sendEmail |
{ to, subject, text, html } |
Promise<void> |
Sends email using Nodemailer with SMTP configuration |
| Parameter | Type | Description |
|---|---|---|
to |
string |
Recipient email address |
subject |
string |
Email subject line |
text |
string |
Plain text email body |
html |
string |
HTML email body |
| Configuration | Description |
|---|---|
| Provider | Nodemailer with SMTP |
| SMTP Settings | From environment variables (EMAIL_HOST, EMAIL_PORT, etc.) |
Email Templates - src/lib/email-templates.ts
Purpose: Generate HTML email templates for authentication flows
| Function | Parameters | Returns | Description |
|---|---|---|---|
verifyEmailTemplate |
url: string |
string |
Generates HTML for email verification email |
resetPasswordEmail |
url: string |
string |
Generates HTML for password reset email |
deleteAccountEmail |
url: string |
string |
Generates HTML for account deletion confirmation |
| Template Feature | Description |
|---|---|
| Branded styling | Consistent Spectrum branding across all emails |
| Responsive design | Mobile-friendly email layouts |
| Call-to-action buttons | Clear action buttons with verification links |
General Utilities - src/lib/utils.ts
Purpose: Provide common utility functions used across the application
| Function | Parameters | Returns | Description |
|---|---|---|---|
cn |
...classes: ClassValue[] |
string |
Merges Tailwind CSS class names with conflict resolution |
| Feature | Description |
|---|---|
| Class name merging | Combines multiple class names intelligently |
| Tailwind merge | Resolves conflicting Tailwind utility classes |
| clsx integration | Supports conditional class names |
| Date formatting helpers | Utility functions for date/time formatting |
| String manipulation | Common string transformation utilities |
Authentication Utilities - src/lib/auth-utils.ts
Purpose: Server-side authentication helper functions
| Function | Description |
|---|---|
| Session validation | Validates user session from request headers |
| User extraction | Extracts user data from session tokens |
| Protected route utilities | Helpers for securing server-side routes |
| Use Case | Description |
|---|---|
| Server Components | Access user session in React Server Components |
| API Routes | Validate authentication in API handlers |
| Middleware | Check authentication in Next.js middleware |
File: src/trpc/init.ts
Purpose: Sets up tRPC with type-safe context.
createTRPCContext = cache(async () => {
return { userId: 'user_123' };
});baseProcedure - Unprotected procedure
- Available to all requests
- No authentication required
protectedProcedure - Authenticated procedure
- Validates session from headers
- Throws
UNAUTHORIZEDif no session - Adds
auth: sessionto context - All routers use this for user-specific data
Transformer: SuperJSON
- Preserves Date objects
- Handles undefined values
- Maintains type fidelity
File: src/trpc/client.tsx
Purpose: Client-side tRPC provider.
Features:
- React Query integration
- Automatic type inference
- SSR-friendly
File: src/trpc/server.tsx
Purpose: Server-side tRPC caller.
Features:
- Server Components support
- Type-safe API calls
- Direct database access
- Credentials: All API keys are encrypted using AES-256 before storage
- Environment Variables: Never commit
.envfile - Authentication: Sessions are validated on every protected endpoint
- Database: Use connection pooling in production
- Rate Limiting: Consider adding rate limiting for API endpoints
- Webhooks: Validate webhook signatures (Stripe, PayPal)
Built with Next.js, Prisma, tRPC, and Inngest