1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Timer types for scheduling future workflow inputs.
//!
//! Timers allow workflows to schedule inputs to be delivered at a future time.
//! Unlike effects (which execute side effects immediately), timers simply
//! deliver an input to the workflow's `decide` function when they fire.
//!
//! # Timers vs Effects
//!
//! | Aspect | Effect | Timer |
//! |--------|--------|-------|
//! | When | Execute now | Execute at scheduled time |
//! | What | Side effect (API call, email, etc.) | Deliver input to workflow |
//! | Handler | `EffectHandler` processes it | `TimerWorker` routes to `Decider` |
//! | Result | May return input to route back | Always delivers its embedded input |
//! | Failure | Transient (retry) or Permanent (dead letter) | Input execution may fail |
//!
//! # Ordering Guarantees
//!
//! When a timer fires, its input is routed through `Decider::execute()`, which
//! acquires an advisory lock on the workflow instance. This means:
//!
//! - Multiple timers firing for the same workflow are serialized
//! - Timer inputs are processed in the same way as external inputs
//! - The workflow sees consistent state when handling timer inputs
//!
//! # Example
//!
//! ```ignore
//! fn decide(now: OffsetDateTime, state: &OrderState, input: &OrderInput)
//! -> Decision<OrderEvent, OrderEffect, OrderInput>
//! {
//! match input {
//! OrderInput::Create { order_id, .. } => {
//! Decision::event(OrderEvent::Created { .. })
//! // Schedule a timeout 1 hour from now
//! .with_timer_after(
//! Duration::from_secs(3600),
//! OrderInput::PaymentTimeout { order_id: order_id.clone() }
//! )
//! }
//!
//! OrderInput::PaymentTimeout { order_id } => {
//! if state.is_paid {
//! // Timer fired but order was already paid
//! Decision::event(OrderEvent::TimeoutIgnored)
//! } else {
//! // Timer fired and order still unpaid - cancel it
//! Decision::event(OrderEvent::Cancelled { reason: "Payment timeout" })
//! }
//! }
//! // ...
//! }
//! }
//! ```
use Duration;
use ;
/// A scheduled timer that will deliver an input to the workflow after a
/// delay. The actual fire time is computed DB-side as `now() + delay`
/// when the timer is written, so this type doesn't need to read the
/// wall clock and decide stays deterministic.
///
/// # Timer keys
///
/// Timers can optionally have a `key` for deduplication. Scheduling a
/// timer with the same key for the same workflow instance replaces the
/// existing one — useful for "reschedule" semantics like extending a
/// timeout.
///
/// # Example
///
/// ```
/// use std::time::Duration;
/// use ironflow::Timer;
///
/// Timer::after(Duration::from_secs(3600), "PaymentTimeout")
/// .with_key("payment-timeout");
/// ```