Skip to content

Timer Event Nodes

Timer event nodes pause workflow execution until a specified time is reached. They support both fixed durations and absolute date/time targets.

Definition

Timer events are defined using TimerEventNodeConfiguration:

dart
final node = LeafWorkflowNode(
  id: 'waitForDeadline',
  type: NodeType.timerEvent,
  name: 'Wait for Deadline',
  config: TimerEventNodeConfiguration(
    timerType: TimerType.duration,
    duration: '24h',
    cancellable: true,
    storeAs: 'timerResult',
  ),
);

Or in JSON:

json
{
  "id": "waitForDeadline",
  "type": "timerEvent",
  "name": "Wait for Deadline",
  "config": {
    "schemaType": "config.timerEvent",
    "timerType": "duration",
    "duration": "24h",
    "cancellable": true,
    "storeAs": "timerResult"
  }
}

Timer Properties

PropertyTypeDescription
timerTypeTimerTypeduration or dateTime
durationString?Duration string (e.g., "5m", "2h30m", "1d")
dateTimeString?ISO 8601 date/time (e.g., "2024-12-25T09:00:00Z")
dateTimeExpressionString?Expression resolving to a date/time from workflow data
storeAsString?Key for storing timer result in output
cancellableboolWhether the timer can be cancelled externally (default: true)

Timer Modes

Duration Mode

Wait for a fixed duration from the current time:

dart
TimerEventNodeConfiguration(
  timerType: TimerType.duration,
  duration: '2h30m',  // 2 hours and 30 minutes
  storeAs: 'timerResult',
)

Supported duration formats:

FormatExampleDescription
Simple"5m", "2h", "1d"Single unit
Compound"1d2h30m", "2h30m15s"Multiple units combined
Milliseconds"500ms"Sub-second precision

Units: d (days), h (hours), m (minutes), s (seconds), ms (milliseconds)

DateTime Mode

Wait until a specific date/time:

dart
// Fixed date/time
TimerEventNodeConfiguration(
  timerType: TimerType.dateTime,
  dateTime: '2024-12-25T09:00:00Z',
  storeAs: 'timerResult',
)

// Dynamic from workflow data
TimerEventNodeConfiguration(
  timerType: TimerType.dateTime,
  dateTimeExpression: r'$.input.dueDate',
  storeAs: 'timerResult',
)

The dateTimeExpression resolves against workflow variables and input data at runtime. This is useful when the target time comes from previous task output or workflow input.

How It Works

Step Details:

  1. Token arrives at timer node
  2. TimerEventNodeProcessor calculates the fire time
  3. If the fire time is already in the past, the workflow continues immediately with firedImmediately: true
  4. Otherwise, returns WaitForTimerResult with timerId, firesAt, and cancellable flag
  5. Engine persists the timer and updates status to waitingForSignal
  6. Timer service checks for fired timers and sends the appropriate signal
  7. On signal received, executor stores result and workflow continues

Timer Output

When a timer fires, the output stored at storeAs contains:

dart
// Normal fire
{
  'timerId': 'uuid-value',
  'firesAt': '2024-12-25T09:00:00.000Z',
  'firedImmediately': false,  // or true if already expired
  'cancelled': false,
}

// Cancelled
{
  'cancelled': true,
  'cancelledAt': '2024-12-20T15:30:00.000Z',
  'reason': 'Cancelled by signal',
}

Cancellation

Timers with cancellable: true can be cancelled by an external signal. The cancel signal name follows the pattern timer_{nodeId}_cancel:

dart
// Cancel a timer
await engine.sendSignal(
  workflowInstanceId: instanceId,
  node: 'waitForDeadline',
  payload: {
    'reason': 'User requested cancellation',
  },
);

Use Cases

SLA Deadline

dart
final slaTimer = LeafWorkflowNode(
  id: 'slaDeadline',
  type: NodeType.timerEvent,
  name: 'SLA Deadline',
  config: TimerEventNodeConfiguration(
    timerType: TimerType.duration,
    duration: '4h',
    cancellable: true,
    storeAs: 'slaResult',
  ),
);

Scheduled Processing

dart
final scheduledNode = LeafWorkflowNode(
  id: 'waitForBusinessHours',
  type: NodeType.timerEvent,
  name: 'Wait for Business Hours',
  config: TimerEventNodeConfiguration(
    timerType: TimerType.dateTime,
    dateTimeExpression: r'$.input.scheduledProcessingTime',
    storeAs: 'scheduleResult',
  ),
);

Cooldown Period

dart
final cooldown = LeafWorkflowNode(
  id: 'cooldownPeriod',
  type: NodeType.timerEvent,
  name: 'Cooldown Period',
  config: TimerEventNodeConfiguration(
    timerType: TimerType.duration,
    duration: '30m',
    cancellable: false,
    storeAs: 'cooldownResult',
  ),
);

Reminder with Cancellation

Combine a timer with cancellation for reminder patterns. If the user completes the task before the timer fires, the timer is cancelled:

dart
// Timer that fires after 24 hours
final reminderTimer = LeafWorkflowNode(
  id: 'reminderTimer',
  type: NodeType.timerEvent,
  name: 'Reminder Timer',
  config: TimerEventNodeConfiguration(
    timerType: TimerType.duration,
    duration: '24h',
    cancellable: true,
    storeAs: 'reminderResult',
  ),
);

Timer vs Signal Wait

AspectTimer EventSignal Wait
TriggerTime-based (automatic)External system (manual)
ConfigurationDuration or date/timeSignal name
CancellationOptional (cancellable flag)N/A
Immediate fireYes, if time already passedNo
Best forDelays, deadlines, SLAsWebhooks, human actions

Validation

The executor validates timer configuration at build time:

  • Duration mode: Duration string must be non-empty and parseable
  • DateTime mode: Either dateTime or dateTimeExpression must be provided
  • DateTime format: Must be valid ISO 8601
  • Expression format: Must be a valid path expression (e.g., $.input.dueDate)

Best Practices

  1. Use duration for relative waits - SLAs, cooldowns, retry delays
  2. Use dateTime for absolute targets - Scheduled processing, business hours
  3. Use expressions for dynamic times - When the target comes from workflow data
  4. Enable cancellation - Unless the wait must not be interrupted
  5. Always use storeAs - Namespace timer results for downstream access
  6. Handle immediate fires - Check firedImmediately if timing matters

Next Steps