Skip to content

Commit fe76e22

Browse files
committed
Fix #1449 - Resolve duration expressions; Improve wait DSL contract
Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com>
1 parent 554aee6 commit fe76e22

11 files changed

Lines changed: 2434 additions & 14 deletions

File tree

docs/superpowers/plans/2026-06-11-waittask-ergonomics.md

Lines changed: 1099 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# WaitTask Ergonomics and Executor Fix
2+
3+
**Date**: 2026-06-11
4+
**Status**: Approved
5+
6+
## Overview
7+
8+
Improve the WaitTask API by adding ergonomic convenience methods to DSL/FuncDSL and fix the WaitExecutor to properly handle all three duration formats (inline, literal, expression).
9+
10+
## Problems
11+
12+
1. DSL has ergonomic `timeout*` methods but no equivalent for wait tasks
13+
2. FuncDSL has zero wait task support
14+
3. WaitExecutor only handles `durationInline` and `durationExpression`, crashes on `durationLiteral`
15+
4. WaitExecutor parses expressions at build time instead of evaluating at runtime
16+
17+
## Solution
18+
19+
### 1. DSL Convenience Methods
20+
21+
Add to `fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java`:
22+
23+
**Unnamed variants:**
24+
```java
25+
public static TasksConfigurer waitDays(int days)
26+
public static TasksConfigurer waitHours(int hours)
27+
public static TasksConfigurer waitMinutes(int minutes)
28+
public static TasksConfigurer waitSeconds(int seconds)
29+
public static TasksConfigurer waitMillis(int milliseconds)
30+
public static TasksConfigurer wait(Duration duration)
31+
```
32+
33+
**Named variants:**
34+
```java
35+
public static TasksConfigurer waitDays(String name, int days)
36+
public static TasksConfigurer waitHours(String name, int hours)
37+
public static TasksConfigurer waitMinutes(String name, int minutes)
38+
public static TasksConfigurer waitSeconds(String name, int seconds)
39+
public static TasksConfigurer waitMillis(String name, int milliseconds)
40+
public static TasksConfigurer wait(String name, Duration duration)
41+
```
42+
43+
**Implementation pattern:**
44+
```java
45+
public static TasksConfigurer waitSeconds(int seconds) {
46+
return list -> list.wait(w -> w.wait(t -> t.duration(d -> d.seconds(seconds))));
47+
}
48+
49+
public static TasksConfigurer waitSeconds(String name, int seconds) {
50+
return list -> list.wait(name, w -> w.wait(t -> t.duration(d -> d.seconds(seconds))));
51+
}
52+
```
53+
54+
The `wait(Duration)` variant converts to `DurationInline` using the existing logic from `WaitTaskBuilder.wait(Duration)`.
55+
56+
### 2. FuncDSL Wait Support
57+
58+
Add to `experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java`:
59+
60+
All methods from section 1, but returning `FuncTaskConfigurer` instead of `TasksConfigurer`, plus the basic wait methods:
61+
62+
```java
63+
public static FuncTaskConfigurer wait(Consumer<TimeoutBuilder> duration)
64+
public static FuncTaskConfigurer wait(String name, Consumer<TimeoutBuilder> duration)
65+
public static FuncTaskConfigurer wait(String durationExpression)
66+
public static FuncTaskConfigurer wait(String name, String durationExpression)
67+
```
68+
69+
These delegate to `list.wait()` on the `FuncTaskItemListBuilder`, following the same pattern as other FuncDSL task methods.
70+
71+
### 3. WaitExecutor Duration Handling
72+
73+
Modify `impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java`:
74+
75+
**Hybrid approach**: Validate static durations at build time, defer expressions to runtime.
76+
77+
**Changes to WaitExecutorBuilder:**
78+
79+
Add field:
80+
```java
81+
private final String runtimeExpression;
82+
```
83+
84+
Update constructor logic:
85+
```java
86+
protected WaitExecutorBuilder(
87+
WorkflowMutablePosition position, WaitTask task, WorkflowDefinition definition) {
88+
super(position, task, definition);
89+
90+
if (task.getWait().getDurationInline() != null) {
91+
this.millisToWait = toLong(task.getWait().getDurationInline());
92+
this.runtimeExpression = null;
93+
} else if (task.getWait().getDurationLiteral() != null) {
94+
this.millisToWait = Duration.parse(task.getWait().getDurationLiteral());
95+
this.runtimeExpression = null;
96+
} else if (task.getWait().getDurationExpression() != null) {
97+
this.millisToWait = null;
98+
this.runtimeExpression = task.getWait().getDurationExpression();
99+
} else {
100+
throw new IllegalStateException("Wait task has no duration specified");
101+
}
102+
}
103+
```
104+
105+
**Changes to WaitExecutor:**
106+
107+
Add field:
108+
```java
109+
private final String runtimeExpression;
110+
```
111+
112+
Update constructor:
113+
```java
114+
protected WaitExecutor(WaitExecutorBuilder builder) {
115+
super(builder);
116+
this.millisToWait = builder.millisToWait;
117+
this.runtimeExpression = builder.runtimeExpression;
118+
}
119+
```
120+
121+
Update `internalExecute()`:
122+
```java
123+
@Override
124+
protected CompletableFuture<WorkflowModel> internalExecute(
125+
WorkflowContext workflow, TaskContext taskContext) {
126+
((WorkflowMutableInstance) workflow.instance()).status(WorkflowStatus.WAITING);
127+
128+
Duration waitDuration;
129+
if (runtimeExpression != null) {
130+
// Evaluate expression at runtime using workflow/task context
131+
String evaluatedExpression = evaluateExpression(runtimeExpression, workflow, taskContext);
132+
waitDuration = Duration.parse(evaluatedExpression);
133+
} else {
134+
waitDuration = millisToWait;
135+
}
136+
137+
return new CompletableFuture<WorkflowModel>()
138+
.completeOnTimeout(taskContext.output(), waitDuration.toMillis(), TimeUnit.MILLISECONDS)
139+
.thenApply(this::complete);
140+
}
141+
```
142+
143+
Note: The `evaluateExpression()` method will need to be implemented or use existing expression evaluation utilities from the workflow context.
144+
145+
## Benefits
146+
147+
- Consistent API between timeout and wait methods
148+
- Better developer experience with concise method calls
149+
- Full support for all three duration formats
150+
- Early validation for static durations
151+
- Proper runtime evaluation for dynamic expressions
152+
- FuncDSL users can now use wait tasks
153+
154+
## Testing Considerations
155+
156+
- Test all convenience methods (days, hours, minutes, seconds, millis)
157+
- Test `wait(Duration)` conversion
158+
- Test WaitExecutor with inline, literal, and expression durations
159+
- Test runtime expression evaluation with various workflow contexts
160+
- Verify build-time validation catches invalid static durations
161+
- Verify runtime errors for invalid expression results

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,18 @@ public FuncTaskItemListBuilder tryCatch(
215215
itemsConfigurer.accept(tryTaskBuilder);
216216
return this.addTaskItem(new TaskItem(name, new Task().withTryTask(tryTaskBuilder.build())));
217217
}
218+
219+
public FuncTaskItemListBuilder wait(
220+
Consumer<io.serverlessworkflow.fluent.spec.WaitTaskBuilder> itemsConfigurer) {
221+
return wait(null, itemsConfigurer);
222+
}
223+
224+
public FuncTaskItemListBuilder wait(
225+
String name, Consumer<io.serverlessworkflow.fluent.spec.WaitTaskBuilder> itemsConfigurer) {
226+
name = this.defaultNameAndRequireConfig(name, itemsConfigurer, "wait");
227+
final io.serverlessworkflow.fluent.spec.WaitTaskBuilder waitTaskBuilder =
228+
new io.serverlessworkflow.fluent.spec.WaitTaskBuilder();
229+
itemsConfigurer.accept(waitTaskBuilder);
230+
return this.addTaskItem(new TaskItem(name, new Task().withWaitTask(waitTaskBuilder.build())));
231+
}
218232
}

0 commit comments

Comments
 (0)