Development & Debugging Nodes

Execute custom code, debug workflows, and log execution data

Development & Debugging Nodes

Sometimes nodes can't do what you need. Write custom code, debug issues, and log execution data. These nodes give you the power to extend DeepChain beyond its built-in capabilities.


Code Node

When to use: When you need custom logic that doesn't fit into standard nodes—complex calculations, custom validation, data transformation, etc.

The Code Node lets you write Dart code that executes as part of your workflow. Access input data, perform any operation, and return results.

Configuration

Configuration:
  code: |
    final result = input['value'] * 2;
    return {'doubled': result};
  timeout: 5000

Code Examples

Example 1: Complex Validation

When to use: Validate data against business rules that are too complex for simple If nodes.

Incoming data:

{
  "email": "alice@example.com",
  "password": "Secure123!",
  "age": 18,
  "country": "US"
}

Code Node:

// Validate registration
final email = input['email'] as String;
final password = input['password'] as String;
final age = input['age'] as int;
final country = input['country'] as String;

// Check email domain
if (!email.endsWith('@company.com')) {
  throw Exception('Email must be from @company.com domain');
}

// Check password strength
if (password.length < 8 || !password.contains(RegExp(r'[!@#$%^&*]'))) {
  throw Exception('Password must be 8+ chars with special characters');
}

// Check age restrictions
if (country == 'US' && age < 18) {
  throw Exception('Must be 18+ in US');
}

return {
  'valid': true,
  'strength': password.length > 12 ? 'strong' : 'medium'
};

If validation fails, the exception is caught and the workflow moves to error handling.

Example 2: Advanced Math/Calculations

When to use: Perform calculations beyond simple arithmetic.

Incoming data:

{
  "principal": 10000,
  "rate": 0.05,
  "years": 10
}

Code Node:

// Compound interest calculation
final principal = input['principal'] as double;
final rate = input['rate'] as double;
final years = input['years'] as int;

final amount = principal * Math.pow(1 + rate, years);
final interest = amount - principal;

return {
  'final_amount': amount.toStringAsFixed(2),
  'interest_earned': interest.toStringAsFixed(2),
  'rounded_amount': amount.round()
};

Example 3: String Manipulation

When to use: Complex text processing beyond regex.

Incoming data:

{
  "text": "Hello World",
  "format": "camelCase"
}

Code Node:

String toCamelCase(String input) {
  final words = input.split(' ');
  if (words.isEmpty) return '';

  String result = words[0].toLowerCase();
  for (int i = 1; i < words.length; i++) {
    result += words[i][0].toUpperCase() + words[i].substring(1).toLowerCase();
  }
  return result;
}

final text = input['text'] as String;
final format = input['format'] as String;

if (format == 'camelCase') {
  return {'result': toCamelCase(text)};
} else if (format == 'snake_case') {
  return {'result': text.toLowerCase().replaceAll(' ', '_')};
} else {
  return {'result': text};
}

Output:

{
  "result": "helloWorld"
}

Example 4: Array Processing

When to use: Transform arrays in ways that would need multiple nodes.

Incoming data:

{
  "users": [
    { "id": 1, "name": "Alice", "age": 28 },
    { "id": 2, "name": "Bob", "age": 35 },
    { "id": 3, "name": "Charlie", "age": 22 }
  ]
}

Code Node:

final users = input['users'] as List<dynamic>;

// Group by age range
final Map<String, List<dynamic>> grouped = {};

for (var user in users) {
  final age = user['age'] as int;
  String group = age < 25 ? 'young' : 'adult';

  if (grouped[group] == null) {
    grouped[group] = [];
  }
  grouped[group]!.add(user);
}

// Sort each group by name
grouped.forEach((key, users) {
  users.sort((a, b) => (a['name'] as String).compareTo(b['name'] as String));
});

return {
  'groups': grouped,
  'total': users.length,
  'average_age': (users.fold(0, (sum, u) => sum + u['age']) / users.length).round()
};

Output:

{
  "groups": {
    "young": [
      { "id": 3, "name": "Charlie", "age": 22 }
    ],
    "adult": [
      { "id": 1, "name": "Alice", "age": 28 },
      { "id": 2, "name": "Bob", "age": 35 }
    ]
  },
  "total": 3,
  "average_age": 28
}

Example 5: Integration with External Logic

When to use: Call libraries or execute complex logic.

Incoming data:

{
  "amount": 100,
  "from_currency": "USD",
  "to_currency": "EUR"
}

Code Node:

// Simple currency conversion (in real workflow, call API)
final Map<String, double> rates = {
  'USD_EUR': 0.92,
  'USD_GBP': 0.79,
  'USD_JPY': 149.50
};

final amount = input['amount'] as double;
final from = input['from_currency'] as String;
final to = input['to_currency'] as String;

if (from == to) {
  return {
    'original': amount,
    'converted': amount,
    'rate': 1.0
  };
}

final rateKey = '${from}_${to}';
final rate = rates[rateKey];

if (rate == null) {
  throw Exception('Conversion rate not found for $from to $to');
}

final converted = amount * rate;

return {
  'original': amount,
  'converted': converted.toStringAsFixed(2),
  'rate': rate
};

Common Code Patterns

Pattern 1: Input Validation

// Always validate inputs before processing
final required = ['user_id', 'email'];

for (var field in required) {
  if (input[field] == null || input[field].toString().isEmpty) {
    throw Exception('Missing required field: $field');
  }
}

// Validate types
if (input['age'] is! int) {
  throw Exception('Age must be an integer');
}

return {'valid': true};

Pattern 2: Error Handling

try {
  // Do something risky
  final result = riskyOperation(input['data']);
  return {'success': true, 'data': result};
} catch (e) {
  // Return error details for workflow handling
  return {
    'success': false,
    'error': e.toString(),
    'timestamp': DateTime.now().toIso8601String()
  };
}

Pattern 3: Conditional Return

if (input['amount'] > 1000) {
  // High value → require approval
  return {
    'requires_approval': true,
    'approval_level': 'manager'
  };
} else {
  // Low value → auto-approve
  return {
    'requires_approval': false,
    'approved': true
  };
}

Log Node

When to use: When you need to debug your workflow—print variables, trace execution, monitor data flow.

The Log Node outputs messages to the workflow logs. Perfect for debugging, monitoring, and understanding what's happening in your workflow.

Configuration

Configuration:
  level: debug | info | warn | error
  message: "Processing {{ input.id }}"
  include_data: true

Log Examples

Example 1: Debug Logging

Log Node Configuration:

level: debug
message: "Processing order: {{ input.order_id }}, Total: ${{ input.total }}"
include_data: true

Output to logs:

[DEBUG] Processing order: ORD-123, Total: $99.99
{
  "order_id": "ORD-123",
  "total": 99.99,
  "customer": "Alice",
  ...
}

Example 2: Info Logging

level: info
message: "Order {{ input.order_id }} approved by {{ input.approver }}"
include_data: false

Output to logs:

[INFO] Order ORD-123 approved by manager@company.com

Example 3: Error Logging

level: error
message: "Payment failed for order {{ input.order_id }}: {{ input.error_message }}"
include_data: true

Perfect for alerting on failures!

Example 4: Warn Logging

level: warn
message: "Order {{ input.order_id }} is high value (${{ input.total }}), flagged for review"
include_data: false

Debug Node

When to use: When you need to inspect the exact state of data flowing through your workflow during development.

The Debug Node pauses execution and opens the debugger, letting you inspect variables, step through code, etc.

Configuration

Configuration:
  enabled: true  # Usually false in production
  break_on_error: true
  inspect_variables:
    - input
    - intermediate_data

Hot Reload Node

When to use: During development, when you want to see changes immediately without restarting the workflow.

The Hot Reload Node enables live code changes while your workflow is running.

Configuration

Configuration:
  enabled: true
  watch_files:
    - /code/my_function.dart

Common Debugging Patterns

Pattern 1: Track Data Through Pipeline

Start
  ↓
Log (Level: info, "Started with: {{ input }}")
  ↓
Data Transform
  ↓
Log (Level: debug, "After transform: {{ output }}")
  ↓
HTTP Request
  ↓
Log (Level: debug, "API response: {{ response }}")
  ↓
Database Insert
  ↓
Log (Level: info, "Inserted record ID: {{ record_id }}")

Each log shows data at each stage.

Pattern 2: Error Tracking

HTTP Request
  ├─ Success → Log (info, "API call succeeded")
  │            ↓
  │            Continue
  │
  └─ ErrorLog (error, "API call failed: {{ error }}")
              ↓
              Code Node (extract error details)
              ↓
              Email (notify ops team)

Pattern 3: Conditional Logging

Code Node
  ├─ High-risk operation → Log (warn, "High risk...")
  └─ Normal operation → Log (debug, "Normal...")

Performance Profiling with Logging

Measure Execution Time

// In Code Node
final startTime = DateTime.now().millisecondsSinceEpoch;

// Do some work
// ...

final duration = DateTime.now().millisecondsSinceEpoch - startTime;

return {
  'result': result,
  'execution_time_ms': duration
};

Then use Log Node:

level: info
message: "Operation took {{ output.execution_time_ms }}ms"

Development Best Practices

Code Quality

  1. Validate all inputs - don't assume data is correct
  2. Use meaningful variable names - final amount = ... not final a = ...
  3. Add comments for complex logic
  4. Test edge cases - null values, empty arrays, negative numbers, etc.

Error Handling

  1. Throw exceptions with helpful messages - not just "Error"
  2. Log before returning - track what went wrong
  3. Handle errors gracefully - don't crash the workflow

Performance

  1. Keep code simple - complex code is slow code
  2. Avoid infinite loops - add safety checks
  3. Use efficient algorithms - don't sort/search large arrays inefficiently
  4. Cache results - don't recalculate same values

Debugging

  1. Use Log nodes liberally - log at each step during development
  2. Check types - ensure inputs are what you expect
  3. Test with real data - test mode can hide issues
  4. Read error messages - they often tell you exactly what went wrong

Next Steps