Skip to content

Gateways

Gateways control workflow routing, enabling conditional branching and parallel execution.

Gateway Types

TypeBuilderDescription
Exclusive.oneOf()Route to exactly ONE path (XOR)
Inclusive.anyOf()Route to ONE OR MORE paths (OR)
Parallel.allOf()Route to ALL paths (AND)

Exclusive Gateway (oneOf)

OneOf Gateway

Routes to exactly ONE path based on the first matching condition.

dart
builder.oneOf('routeDecision', [
  Branch.whenFn(
    (output) => output['amount'] > 10000,
    then: 'requireManagerApproval',
    label: 'High Value',
  ),
  Branch.whenFn(
    (output) => output['amount'] > 1000,
    then: 'requireSupervisorApproval',
    label: 'Medium Value',
  ),
  Branch.otherwise(then: 'autoApprove'),
]);

Behavior

  1. Evaluates conditions in priority order (highest first)
  2. Takes the first matching path
  3. Falls back to default branch if no match

Custom Condition Executors

dart
class HighValueCondition extends ConditionExecutor {
  const HighValueCondition() : super(schemaType: 'condition.highValue');

  @override
  Future<bool> execute(ExecutionContext context) async {
    // Use get<T> for previous node output
    return (context.get<num>('amount') ?? 0) > 10000;
  }

  @override
  Map<String, dynamic> toJson() => {'schemaType': schemaType};

  static final typeDescriptor = TypeDescriptor<ConditionExecutor>(
    schemaType: 'condition.highValue',
    fromJson: (_) => const HighValueCondition(),
  );
}

// Register via descriptor
final descriptor = WorkflowDescriptor(
  title: 'Custom Conditions',
  conditions: [HighValueCondition.typeDescriptor],
);

// Use expression-based conditions in builder (simpler)
builder.oneOf('routeByValue', [
  Branch.when("amount > 10000", then: 'highValuePath'),
  Branch.otherwise(then: 'normalPath'),
]);

Inclusive Gateway (anyOf)

Race multiple paths - first to complete wins.

dart
builder.anyOf('notifyRace', [
  'emailNotification',
  'smsNotification',
  'pushNotification',
]);

Behavior

  1. Starts ALL paths concurrently
  2. First path to complete wins
  3. Other paths are cancelled

WARNING

Use anyOf for race conditions where you want the fastest path to win.

Parallel Gateway (allOf)

AllOf Gateway

Routes to ALL paths unconditionally, enabling concurrent execution.

Fork (Split)

dart
builder.allOf('parallelStart', [
  'validateInventory',
  'processPayment',
  'sendConfirmation',
]);

Join (Synchronize)

The same gateway type is used for joining parallel branches:

dart
// Fork
builder.allOf('startParallel', ['taskA', 'taskB', 'taskC']);

// Join - all branches must arrive before continuing
builder.allOf('joinParallel', ['afterJoin']);

// Edges
builder
    .connect('startParallel', 'taskA')
    .connect('startParallel', 'taskB')
    .connect('startParallel', 'taskC')
    .connect('taskA', 'joinParallel')
    .connect('taskB', 'joinParallel')
    .connect('taskC', 'joinParallel')
    .connect('joinParallel', 'afterJoin');

Visual

Join Behavior

  1. Join waits for ALL incoming branches
  2. Each arriving token is deactivated
  3. When all arrive, a new token is created
  4. Execution continues past the join

Branch Definition

dart
class Branch {
  // Expression-based condition
  static Branch when(String expression, {required String then, String? label});

  // Function-based routing (inline)
  static Branch whenFn(ConditionEvaluator evaluator, {required String then, String? label});

  // Equality check
  static Branch whenEquals(String variable, Object value, {required String then});

  // Boolean check
  static Branch whenTrue(String variable, {required String then});

  // Null check
  static Branch whenNotNull(String variable, {required String then});

  // Default/fallback
  static Branch otherwise({required String then});
}

Priority

Conditions are evaluated in the order they appear in the list:

dart
builder.oneOf('decide', [
  Branch.whenFn((o) => o['urgent'] == true, then: 'urgentPath'),  // Evaluated first
  Branch.whenFn((o) => o['important'] == true, then: 'importantPath'),  // Evaluated second
  Branch.otherwise(then: 'normalPath'),  // Fallback
]);

The first matching condition wins - order your branches from most specific to least specific.

Common Patterns

Approval Routing

dart
builder.oneOf('routeApproval', [
  Branch.whenEquals('decision', 'approved', then: 'handleApproved'),
  Branch.whenEquals('decision', 'rejected', then: 'handleRejected'),
  Branch.whenEquals('decision', 'revision', then: 'requestRevision'),
  Branch.otherwise(then: 'handleRejected'),
]);

Loop Control

dart
builder.oneOf('checkContinue', [
  Branch.whenTrue('hasMore', then: 'loopBody'),
  Branch.otherwise(then: 'exitLoop'),
]);

Error Handling

dart
builder.oneOf('checkResult', [
  Branch.whenTrue('success', then: 'continueProcess'),
  Branch.when("error.retryable == true", then: 'retryTask'),
  Branch.otherwise(then: 'handleError'),
]);

Best Practices

  1. Always include default branch - Catch unexpected cases
  2. Use descriptive labels - Document the routing logic
  3. Keep conditions simple - Complex logic in executor classes
  4. Test all paths - Ensure coverage of all branches
  5. Name gateways clearly - routeByAmount not gateway1

Next Steps