Skip to content

Quick Start

Build and run a small workflow with the current CDX engine API.

What You Will Build

This example:

  1. Validates the workflow input.
  2. Waits for an external decision signal.
  3. Routes to approved or rejected work.
  4. Stores runtime state in InMemoryStorage.

Complete Example

dart
import 'package:vyuh_workflow_engine/vyuh_workflow_engine.dart';

Future<void> main() async {
  final context = RegistryTypeResolver(
    descriptors: [DefaultWorkflowDescriptor()],
  );

  final storage = InMemoryStorage();

  final engine = WorkflowEngine(
    context: context,
    storage: storage,
    executionMode: ExecutionMode.production,
  );
  await engine.initialize();

  final workflow = WorkflowBuilder(
    'request-approval',
    'Request Approval',
    description: 'Validate a request and wait for an approval signal.',
  )
      .start('start')
      .task('validate', execute: (ctx) async {
        final requestId = ctx.getInitialRequired<String>('requestId');

        return {
          'requestId': requestId,
          'validatedAt': DateTime.now().toUtc().toIso8601String(),
        };
      })
      .signalWait(
        'await-decision',
        signal: 'approval_decision',
        storeAs: 'decision',
      )
      .oneOf('route-decision', [
        Branch.whenEquals('decision.status', 'approved', then: 'mark-approved'),
        Branch.whenEquals('decision.status', 'rejected', then: 'mark-rejected'),
        Branch.otherwise(then: 'mark-rejected'),
      ])
      .task('mark-approved', execute: (ctx) async {
        return {
          'finalStatus': 'approved',
          'approvedBy': ctx.getAny<String>('decision.userId'),
        };
      })
      .end('approved')
      .from('route-decision')
      .to('mark-rejected')
      .task('mark-rejected', execute: (ctx) async {
        return {
          'finalStatus': 'rejected',
          'rejectedBy': ctx.getAny<String>('decision.userId'),
        };
      })
      .end('rejected')
      .build();

  engine.registerWorkflow(workflow);

  final instance = await engine.startWorkflow(
    workflowCode: workflow.code,
    input: {'requestId': 'REQ-001'},
    userId: 'alice',
  );

  await engine.sendSignal(
    workflowInstanceId: instance.id,
    node: 'await-decision',
    payload: {
      'status': 'approved',
      'userId': 'manager-1',
      'comment': 'Looks good.',
    },
  );

  final completed = await engine.getWorkflowInstance(instance.id);
  print(completed?.status);
  print(completed?.output);

  await engine.dispose();
}

Runtime Shape

Current API Points

APICurrent behavior
RegistryTypeResolverResolves node configs, task executors, user task executors, and conditions from WorkflowDescriptors.
InMemoryStorage()Uses typed WorkflowDefinition, WorkflowInstance, UserTaskInstance, and WorkflowEvent repositories. It no longer takes a context.
WorkflowEngineRequires context, storage, and executionMode. Use ExecutionMode.production for real execution.
WorkflowBuilderBuilds an executable Workflow with generated ID ${code}-v${version}.
TaskExecutorReturns TaskSuccess or TaskFailure; inline builder tasks can return plain maps and are wrapped for you.
sendSignalMatches waiting nodes by node ID, schema type, or signal name. Optional port chooses a user-task or multi-output route.

Next Steps