Actions System Architecture

Technical overview of Actions system architecture and implementation patterns

actionsarchitecturetechnical

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 TenantEntityWithAccess for multi-tenant support
  • Now includes required title field for user identification
  • Optional description field 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:

  1. Document operation occurs (create/update/delete)
  2. Server detects change and identifies applicable Actions
  3. Executor invokes Actions with matching trigger names
  4. 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 ActionBlockRegistry by name
  • Server blocks are registered in createServerBlockRegistry()
  • Each block implements the ActionBlockImpl interface
  • 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:

  1. Trigger Matching: Find Actions with matching trigger names
  2. Sequential Execution: Process each matching Action
  3. Connection Following: Execute connected blocks based on socket connections
  4. Error Handling: Capture errors and continue execution
  5. 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 DataViewComponent with ActionSchema
  • Displays title, description, and metadata
  • Provides create, edit, and delete operations
  • Integrated search and filtering

Create/Edit Dialog (NewActionDialog.svelte):

  • Uses FormComponent with ActionSchema
  • 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 ActionBlockImpl interface
  • Register in appropriate registry (client/server)
  • Define input/output sockets
  • Handle configuration parameters

2. Custom Triggers

  • Add new trigger types to ActionTriggers enum
  • Implement trigger detection logic
  • Ensure proper data structure for trigger events

3. Custom Socket Types

  • Extend ActionSocketType enum
  • 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