Actions System Architecture
Technical overview of Actions system architecture and implementation patterns
Actions System Architecture
This document provides a detailed technical overview of the Actions system architecture, including component relationships, data flow, and implementation patterns.
High-Level Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client │ │ Shared │ │ Server │
│ (Frontend) │ │ (Business │ │ (Backend) │
│ │ │ Logic) │ │ │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • Visual Editor │────│ • Action Types │────│ • Block Registry│
│ • Action UI │ │ • Executor │ │ • Server Blocks │
│ • Workflow View │ │ • Validation │ │ • Trigger Logic │
│ • CRUD Interface│ │ • Schema │ │ • REST API │
│ • DataView │ │ • TypeSchema │ │ • Persistence │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Core Components
1. Action Definition (packages/shared/src/actions/action.ts)
The central interface that defines a complete workflow:
interface Action extends TenantEntityWithAccess {
title: string; // Required descriptive name
description?: string; // Optional detailed explanation
triggers?: ActionTrigger[]; // What starts the workflow
blocks?: ActionBlock[]; // Processing units
connections?: ActionSocketConnection[]; // Data flow
}
Key Properties:
- Inherits from
TenantEntityWithAccessfor multi-tenant support - Now includes required
titlefield for user identification - Optional
descriptionfield for detailed workflow documentation - Contains arrays of triggers, blocks, and connections
- Represents a complete executable workflow
- Integrated with TypeSchema for automatic form generation
2. Action Triggers (packages/shared/src/actions/action-trigger.ts)
Define when workflows should execute:
enum ActionTriggers {
DOCUMENT_CREATED = "documentCreated",
DOCUMENT_UPDATED = "documentUpdated",
DOCUMENT_DELETED = "documentDeleted"
}
interface ActionTrigger {
id: string;
triggerName: string; // Must match ActionTriggers enum
configuration?: { [key: string]: any };
}
Trigger Data Flow:
- Document operation occurs (create/update/delete)
- Server detects change and identifies applicable Actions
- Executor invokes Actions with matching trigger names
- Trigger data flows to connected blocks
3. Action Blocks (packages/shared/src/actions/action-block.ts)
Reusable processing units that perform specific tasks:
interface ActionBlock {
id: string;
blockName: string; // Must match registered block name
configuration?: { [key: string]: any };
}
interface ActionBlockImpl {
execute: (socketName: string, data: any) => Promise<ActionBlockResult>;
}
Block Registry Pattern:
- Blocks are registered in
ActionBlockRegistryby name - Server blocks are registered in
createServerBlockRegistry() - Each block implements the
ActionBlockImplinterface - Blocks are stateless and tenant-aware
4. Socket System (packages/shared/src/actions/action-socket.ts)
Provides type-safe data flow between components:
interface ActionSocket {
name: string;
type: ActionSocketType; // Data type validation
direction: ActionSocketDirection; // IN or OUT
}
interface ActionSocketConnection {
sourceBlockId: string; // Where data comes from
sourceSocketName: string; // Output socket name
targetBlockId: string; // Where data goes to
targetSocketName: string; // Input socket name
}
Socket Types:
- Primitive:
string,number,boolean - Arrays:
string[],number[],boolean[] - Complex:
object,object[] - Special:
null
5. Action Executor (packages/shared/src/actions/action-executor.ts)
Orchestrates workflow execution:
interface ActionExecutor {
executeActionsByTrigger({
triggerName,
data
}: {
triggerName: string;
data: any;
}): Promise<ActionExecutionResult[]>;
}
Execution Flow:
- Trigger Matching: Find Actions with matching trigger names
- Sequential Execution: Process each matching Action
- Connection Following: Execute connected blocks based on socket connections
- Error Handling: Capture errors and continue execution
- Logging: Record execution details for debugging
User Interface Components
6. Visual Flow Editor (apps/backoffice/src/routes/.../ActionFlowEditor.svelte)
Provides an interactive canvas for workflow creation using @xyflow/svelte:
interface FlowEditorProps {
action: Action;
onTriggerAdd: () => void;
onBlockAdd: () => void;
}
Visual Elements:
- Nodes: Represent triggers (blue) and blocks (green)
- Edges: Show connections and data flow
- Canvas: Interactive area with zoom, pan, and drag capabilities
- Controls: Zoom controls and fit-to-view functionality
Node Rendering:
// Trigger nodes
{
id: `trigger-${trigger.id}`,
type: 'default',
position: { x: 100, y: 100 + index * 120 },
data: {
label: `🔗 ${trigger.triggerName}`,
trigger
}
}
// Block nodes
{
id: `block-${block.id}`,
type: 'default',
position: { x: 400, y: 100 + index * 120 },
data: {
label: `⚙️ ${block.blockName}`,
block
}
}
7. Backoffice CRUD Interface
Complete management interface integrated with the project settings:
Actions List Page (+page.svelte):
- Uses
DataViewComponentwithActionSchema - Displays title, description, and metadata
- Provides create, edit, and delete operations
- Integrated search and filtering
Create/Edit Dialog (NewActionDialog.svelte):
- Uses
FormComponentwithActionSchema - Validates required title field
- Optional description field
- REST API integration for persistence
REST API Endpoints:
// CRUD operations
GET /api/projects/[projectId]/actions
POST /api/projects/[projectId]/actions
GET /api/projects/[projectId]/actions/[actionId]
PUT /api/projects/[projectId]/actions/[actionId]
DELETE /api/projects/[projectId]/actions/[actionId]
8. Component Selection System
Modal-based selection for adding triggers and blocks:
TriggerBlockSelectionModal.svelte:
- Trigger Options: Document Created/Updated/Deleted
- Block Options: Send Email, Update Database, Call Webhook, Conditional Logic
- Interactive Selection: Click-to-add interface
- Type Safety: Ensures valid trigger/block names
Available Components:
// Trigger Types
const AVAILABLE_TRIGGERS = [
{ name: "documentCreated", label: "Document Created", icon: "🔗" },
{ name: "documentUpdated", label: "Document Updated", icon: "🔗" },
{ name: "documentDeleted", label: "Document Deleted", icon: "🔗" }
];
// Block Types
const AVAILABLE_BLOCKS = [
{ name: "send-mail", label: "Send Email", icon: "⚙️" },
{ name: "update-database", label: "Update Database", icon: "⚙️" },
{ name: "call-webhook", label: "Call Webhook", icon: "⚙️" },
{ name: "conditional-logic", label: "Conditional Logic", icon: "⚙️" }
];
Data Flow Architecture
1. Trigger Execution Flow
Document Change → Trigger Detection → Action Matching → Block Execution
↓ ↓ ↓ ↓
User Action Server Service Executor Logic Block Registry
2. Block Connection Flow
Trigger
↓ (connection: trigger → block1.in)
Block 1 (execute)
↓ (result: block1.out → block2.in)
Block 2 (execute)
↓ (result: block2.success → block3.in)
Block 3 (execute)
3. Error Handling Flow
Block Execution Error
↓
Capture Error Message
↓
Log to ActionExecutionResult
↓
Continue with Next Block/Action
↓
Return All Results (success + failures)
Implementation Patterns
1. Registry Pattern
Block Registration:
// Server registry creation
export function createServerBlockRegistry(): ActionBlockRegistry {
const registry = new ActionBlockRegistry();
registry.registerBlock(BLOCK_SEND_MAIL, {
execute: sendMailBlockFn
});
return registry;
}
Usage Benefits:
- Decoupled block implementations
- Easy to add new block types
- Dynamic block discovery
- Testable in isolation
2. Socket-Based Communication
Type Safety:
enum ActionSocketType {
STRING = "string",
OBJECT = "object",
// ... other types
}
Connection Validation:
- Source and target socket types must be compatible
- Socket names must exist on respective blocks
- Connections form a directed acyclic graph (DAG)
3. Async Execution Model
Sequential Processing:
- Actions execute sequentially within a trigger
- Blocks within an Action execute based on connections
- Each block execution is awaited before proceeding
- Errors don't stop the entire execution chain
4. Multi-Tenant Architecture
Tenant Isolation:
- Actions inherit from
TenantEntityWithAccess - Block execution receives tenant context
- Data is scoped to the tenant
- Access control enforced at Action level
Performance Considerations
1. Execution Efficiency
- Blocks execute sequentially (not parallel) for predictable ordering
- Registry lookup is O(1) for block resolution
- Connection traversal is O(n) where n = number of connections
2. Memory Usage
- Stateless block implementations
- Data flows through connections without persistence
- Execution results include only logs and final output
3. Error Recovery
- Individual block failures don't halt entire workflows
- Execution continues with remaining Actions/blocks
- Comprehensive logging for debugging
Extension Points
1. Custom Blocks
- Implement
ActionBlockImplinterface - Register in appropriate registry (client/server)
- Define input/output sockets
- Handle configuration parameters
2. Custom Triggers
- Add new trigger types to
ActionTriggersenum - Implement trigger detection logic
- Ensure proper data structure for trigger events
3. Custom Socket Types
- Extend
ActionSocketTypeenum - Implement type validation
- Ensure serialization compatibility
Testing Strategy
1. Unit Tests
- Individual block implementations
- Registry functionality
- Socket type validation
- Connection logic
2. Integration Tests
- Complete workflow execution
- Error handling scenarios
- Multi-tenant data isolation
- Performance under load
3. End-to-End Tests
- UI workflow creation
- Trigger-to-completion flows
- Real-world usage scenarios
- Cross-component integration