Workflow
A Workflow is the executable runtime graph. A WorkflowDefinition is the persisted design-time graph. The engine converts definitions into executable workflows by resolving node configurations, conditions, and executors through a WorkflowTypeResolver.
Definition vs Executable
| Model | Where used | Contains |
|---|---|---|
WorkflowDefinition | Storage, JSON, editor library, template seeds | Raw node/edge definition data suitable for persistence. |
Workflow | Engine execution cache | Typed node configs, executable graph helpers, and resolved node processors. |
Storage repositories store WorkflowDefinition. The engine executes Workflow.
Loading
Load from storage:
final definition = await engine.storage.workflows.getByCode(
'approval',
version: 4,
);
if (definition != null) {
final workflow = engine.loadWorkflowDefinition(definition);
print('${workflow.code} v${workflow.version}');
}Load from JSON:
final workflow = engine.loadWorkflowJSON(json);Register a code-built workflow:
final workflow = WorkflowBuilder('order', 'Order Workflow')
.start('start')
.task('validate', execute: (ctx) async => {'valid': true})
.end('done')
.build();
engine.registerWorkflow(workflow);Workflow Shape
class Workflow {
final String? id;
final String code;
final String name;
final String? description;
final int version;
final bool isActive;
final List<WorkflowNode> nodes;
final List<WorkflowEdge> edges;
final WorkflowTimeouts timeouts;
final RetryPolicy? defaultRetryPolicy;
final Map<String, dynamic>? inputSchema;
final Map<String, dynamic> metadata;
final List<String> tags;
final DateTime? createdAt;
final DateTime? updatedAt;
}WorkflowBuilder.build() generates an ID as ${code.toLowerCase()}-v${version}. Stored definitions usually carry database-generated IDs.
Nodes
Workflow nodes are typed by NodeType and carry a NodeConfiguration:
switch (node.config) {
case ServiceTaskNodeConfiguration config:
print(config.schemaType);
case UserTaskNodeConfiguration config:
print(config.title);
case SignalEventNodeConfiguration config:
print(config.signalName);
}The resolver chooses the configuration class from the node type. The schemaType inside task and user-task configs identifies the executor, not the configuration class.
Edges
Edges connect source and target nodes and may include:
sourcePortIdfor output-port routing.conditionfor gateway decisions.priorityfor ordered branch evaluation.isDefaultfor fallback routing.
class WorkflowEdge {
final String id;
final String sourceNodeId;
final String targetNodeId;
final String? sourcePortId;
final ConditionExecutor? condition;
final int priority;
final bool isDefault;
}Registration
The engine has an in-memory executable workflow cache. It is populated by:
registerWorkflow(workflow).loadWorkflowDefinition(definition).loadWorkflowJSON(json).startWorkflow(...), when the requested workflow is not already cached and can be loaded from storage.
Registration validates the workflow and resolves node processors.
Versioning
Start latest active version by code:
await engine.startWorkflow(
workflowCode: 'approval',
input: input,
);Start a specific version:
await engine.startWorkflow(
workflowCode: 'approval',
version: 4,
input: input,
);Start by stored definition ID:
await engine.startWorkflow(
workflowId: workflowDefinitionId,
input: input,
);Running instances keep their workflowId, status, tokens, input, output, and metadata. Updating a definition does not rewrite already-running instances.
Serialization
Use WorkflowDefinition for persisted JSON. If you have a Workflow and want to persist it:
await engine.saveWorkflow(workflow);If you have raw JSON:
final workflow = engine.loadWorkflowJSON(json);Avoid direct Workflow.fromJson() unless you explicitly run it inside a WorkflowTypeResolver scope. WorkflowEngine loading methods handle the resolver scope for you.
Validation
final errors = workflow.validate();
if (errors.isNotEmpty) {
throw WorkflowValidationException(errors);
}The engine validates automatically when loading or registering workflows.