ArchitectureControls

Expression Control

Expression controls provide a powerful JavaScript code editor that allows users to input executable code that is evaluated at runtime. This control is designed for advanced users who need dynamic data transformation, complex calculations, or conditional logic within their workflows.

Basic Usage

import * as i from '@xentom/integration-framework';

// Simple expression control
i.controls.expression({
  label: 'Data Transform',
  placeholder: 'Enter JavaScript expression...',
  defaultValue: {
    result: 'computed value',
  },
});

// Multi-line code editor
i.controls.expression({
  label: 'Custom Logic',
  rows: 8,
  placeholder: 'function transform(data) {\n  return data;\n}',
});

Configuration Options

Prop

Type

Important Note

Expression controls are only supported for node pins, not environment variables. This is because expressions need access to runtime data and workflow context that isn't available during environment configuration.

Runtime Context

Expression controls have access to runtime variables and data that flows through the workflow:

dataProcessor: i.nodes.callable({
  inputs: {
    sourceData: i.pins.data({
      control: i.controls.text({
        placeholder: 'Enter source data...',
      }),
    }),
    transformExpression: i.pins.data({
      control: i.controls.expression({
        label: 'Transform Expression',
        description: 'JavaScript code to transform the data',
        rows: 6,
        placeholder:
          '// Transform the data\nreturn {\n  processed: data.map(item => item.value * 2)\n};',
      }),
    }),
  },

  outputs: {
    result: i.pins.data(),
  },

  run({ inputs, next }) {
    // The expression has access to:
    // - inputs from this node
    // - variables from workflow context
    // - previous node outputs

    const result = evaluateExpression(inputs.transformExpression, {
      data: inputs.sourceData,
      // ... other context variables
    });

    next({ result });
  },
});

Common Use Cases

Data Transformation

transform: i.pins.data({
  control: i.controls.expression({
    label: 'Data Mapping',
    description: 'Transform input data to desired output format',
    rows: 8,
    defaultValue: {
      name: 'data.firstName + " " + data.lastName',
      email: 'data.email.toLowerCase()',
      createdAt: 'new Date().toISOString()',
    },
  }),
});

Conditional Logic

condition: i.pins.data({
  control: i.controls.expression({
    label: 'Condition Check',
    description: 'Boolean expression to determine flow path',
    placeholder: 'user.age >= 18 && user.verified === true',
    defaultValue: 'data.status === "active"',
  }),
});

Complex Calculations

calculator: i.pins.data({
  control: i.controls.expression({
    label: 'Price Calculation',
    description: 'Calculate final price with taxes and discounts',
    rows: 10,
    defaultValue: `
// Calculate total with tax and discount
const subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const discount = subtotal * (discountPercent / 100);
const taxableAmount = subtotal - discount;
const tax = taxableAmount * (taxRate / 100);
const total = taxableAmount + tax;

return {
  subtotal,
  discount,
  tax,
  total
};`,
  }),
});

API Response Processing

responseProcessor: i.pins.data({
  control: i.controls.expression({
    label: 'Response Processor',
    description: 'Extract and format data from API response',
    rows: 12,
    defaultValue: `
// Process API response
if (!response.success) {
  throw new Error('API request failed: ' + response.error);
}

return response.data.map(user => ({
  id: user.id,
  name: user.full_name,
  email: user.email_address,
  lastLogin: new Date(user.last_login_timestamp).toISOString(),
  isActive: user.status === 'active'
}));`,
  }),
});

Built-in Functions and Variables

Expression controls have access to standard JavaScript functions plus workflow-specific variables:

// Available in expressions:
// - Standard JavaScript functions (Math, Date, JSON, etc.)
// - Workflow variables and data from connected nodes
// - Custom utility functions (if provided by the integration)

utilityExpression: i.pins.data({
  control: i.controls.expression({
    label: 'Utility Functions',
    rows: 8,
    defaultValue: `
// Standard JavaScript is available
const now = new Date();
const timestamp = Math.floor(now.getTime() / 1000);

// Access workflow data
const processedData = inputData.map(item => ({
  ...item,
  processed: true,
  timestamp: timestamp,
  hash: btoa(JSON.stringify(item)) // Base64 encoding
}));

return processedData;`,
  }),
});

Error Handling

Expressions should include proper error handling:

safeProcessor: i.pins.data({
  control: i.controls.expression({
    label: 'Safe Data Processor',
    description: 'Process data with error handling',
    rows: 15,
    defaultValue: `
try {
  // Validate input
  if (!data || !Array.isArray(data)) {
    throw new Error('Input must be an array');
  }
  
  // Process each item
  const results = data.map((item, index) => {
    try {
      return {
        id: item.id || index,
        value: parseFloat(item.value) || 0,
        processed: true
      };
    } catch (itemError) {
      console.warn('Error processing item', index, ':', itemError.message);
      return {
        id: index,
        value: 0,
        processed: false,
        error: itemError.message
      };
    }
  });
  
  return results;
} catch (error) {
  console.error('Processing failed:', error.message);
  return { error: error.message };
}`,
  }),
});

Best Practices

Provide Clear Examples

// Good - Clear example with comments
filterExpression: i.pins.data({
  control: i.controls.expression({
    label: 'Filter Expression',
    description: 'Filter array items based on criteria',
    placeholder:
      '// Filter active users over 18\ndata.filter(user => user.active && user.age > 18)',
    defaultValue: 'data.filter(item => item.status === "active")',
  }),
});

Use Descriptive Names

// Good - Clear purpose
priceCalculation: i.pins.data({
  control: i.controls.expression({
    label: 'Price Calculation Logic',
    description: 'Calculate final price including tax and discounts',
  }),
});

// Avoid - Vague naming
expression: i.pins.data({
  control: i.controls.expression({
    label: 'Expression', // Too generic
  }),
});

Appropriate Row Count

Choose row count based on expected code complexity:

// Simple expressions - single line or few rows
simpleFilter: i.pins.data({
  control: i.controls.expression({
    rows: 2,
    placeholder: 'item => item.active',
  }),
});

// Complex logic - more rows
complexProcessor: i.pins.data({
  control: i.controls.expression({
    rows: 15,
    placeholder:
      'function processData(data) {\n  // Complex processing logic\n}',
  }),
});

Provide Helpful Descriptions

validation: i.pins.data({
  control: i.controls.expression({
    label: 'Validation Rules',
    description:
      'Return true if data is valid, false otherwise. Available variables: data, user, config',
    placeholder: 'data.email.includes("@") && data.age >= 13',
    rows: 4,
  }),
});

Expression controls provide immense flexibility for advanced users while maintaining the visual workflow approach of Xentom. They bridge the gap between no-code simplicity and full programming power.

On this page