Skip to content

Workflow Descriptor

WorkflowDescriptor is the registration bundle for executable workflow behavior.

Stored workflow definitions refer to executable logic by schemaType. Descriptors map those schema types to factories.

Contract

dart
class WorkflowDescriptor {
  const WorkflowDescriptor({
    this.tasks = const [],
    this.userTasks = const [],
    this.conditions = const [],
    this.nodeProcessors = const [],
    this.lifecycleHooks = const {},
    this.title,
    this.description,
  });

  final List<TypeDescriptor<TaskExecutor>> tasks;
  final List<TypeDescriptor<UserTaskExecutor>> userTasks;
  final List<TypeDescriptor<ConditionExecutor>> conditions;
  final List<NodeProcessor> nodeProcessors;
  final Map<String, WorkflowLifecycleHooks> lifecycleHooks;
  final String? title;
  final String? description;
}

Basic Usage

dart
final descriptor = WorkflowDescriptor(
  title: 'Document executors',
  tasks: [
    ValidateDocumentExecutor.typeDescriptor,
    ApplyDocumentOutcomeExecutor.typeDescriptor,
  ],
  userTasks: [
    ApprovalUserTaskExecutor.typeDescriptor,
  ],
  conditions: [
    RequiresManagerCondition.typeDescriptor,
  ],
);

final context = RegistryTypeResolver(
  descriptors: [
    DefaultWorkflowDescriptor(),
    descriptor,
  ],
);

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

DefaultWorkflowDescriptor

Include DefaultWorkflowDescriptor() unless you are deliberately replacing all built-in behavior. It registers the engine's default processors and built-in condition types.

Built-in processors include:

  • StartEventNodeProcessor
  • EndEventNodeProcessor
  • TaskNodeProcessor
  • UserTaskNodeProcessor
  • gateway processors
  • signal, timer, subflow, and loop processors

TypeDescriptor

Task, user-task, and condition descriptors use TypeDescriptor<T>:

dart
static final typeDescriptor = TypeDescriptor<TaskExecutor>(
  schemaType: 'task.document.validate',
  fromJson: (_) => ValidateDocumentExecutor(),
  title: 'Validate Document',
  description: 'Validates document input before approval.',
);

The schemaType must match the node configuration or condition JSON.

Lifecycle Hooks

Descriptors can also register lifecycle hooks keyed by workflow code:

dart
final descriptor = WorkflowDescriptor(
  lifecycleHooks: {
    'approval': WorkflowLifecycleHooks(
      onCancel: (instance, reason) async {
        await draftService.release(instance.correlationId);
      },
    ),
  },
);

The engine looks up hooks during administrative lifecycle operations such as cancellation.

Descriptor Order

Descriptors are processed in order by RegistryTypeResolver. Keep defaults first:

dart
final context = RegistryTypeResolver(
  descriptors: [
    DefaultWorkflowDescriptor(),
    sharedDescriptor,
    appDescriptor,
  ],
);

Avoid duplicate schema types unless overriding is intentional.

Template Descriptors

Workflow templates expose descriptors too:

dart
final template = ApprovalTemplate(domains: [approvalDomain]);

final context = RegistryTypeResolver(
  descriptors: [
    DefaultWorkflowDescriptor(),
    template.buildDescriptor(),
  ],
);

For the canonical approval template, this descriptor registers approval loop executors, outcome dispatchers, approval user tasks, and revision user tasks.

What Descriptors Do Not Own

Descriptors do not:

  • Persist workflows.
  • Load workflow definitions.
  • Create storage tables.
  • Register node configuration classes. Current node configuration resolution is based on node type.

See Also