Control Flow
WHILE and UNTIL give you loops; BREAK and CONTINUE give you control
inside those loops. Together with IF, they let you express branching logic
declaratively — without dropping back to hand-written for loops.
BREAK — exit the loop
Section titled “BREAK — exit the loop”BREAK terminates the enclosing loop immediately when its predicate returns
true. Execution continues with the next activity after the loop.
import {ActionBuilder, ACTIVITY} from "@gesslar/actioneer"
class BreakExample { setup(builder) { builder .do("initialize", ctx => { ctx.count = 0 ctx.items = [] return ctx }) .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100, new ActionBuilder() .do("increment", ctx => { ctx.count++ ctx.items.push(ctx.count) return ctx }) .do("earlyExit", ACTIVITY.BREAK, ctx => ctx.count >= 5) ) .do("finish", ctx => { return ctx.items }) // [1, 2, 3, 4, 5] }}The loop predicate would allow up to 100 iterations, but BREAK exits once
count reaches 5.
CONTINUE — skip to the next iteration
Section titled “CONTINUE — skip to the next iteration”CONTINUE skips the remaining activities in the current iteration when its
predicate returns true, then continues with the next iteration. For WHILE,
the loop predicate is re-evaluated; for UNTIL, the operation runs again and
the predicate is checked after.
import {ActionBuilder, ACTIVITY} from "@gesslar/actioneer"
class ContinueExample { setup(builder) { builder .do("initialize", ctx => { ctx.count = 0 ctx.processed = [] return ctx }) .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 5, new ActionBuilder() .do("increment", ctx => { ctx.count++ return ctx }) .do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0) .do("process", ctx => { ctx.processed.push(ctx.count) return ctx }) ) .do("finish", ctx => { return ctx.processed }) // [1, 3, 5] }}When count is even, CONTINUE skips the process step, so only odd numbers
are collected.
Combining control flow
Section titled “Combining control flow”IF, BREAK, and CONTINUE compose freely within a single loop. Order
matters — activities run top to bottom each iteration.
class CombinedExample { setup(builder) { builder .do("initialize", ctx => { ctx.count = 0 ctx.results = [] return ctx }) .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100, new ActionBuilder() .do("increment", ctx => { ctx.count++; return ctx }) .do("exitAt10", ACTIVITY.BREAK, ctx => ctx.count > 10) .do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0) .do("processLarge", ACTIVITY.IF, ctx => ctx.count > 5, ctx => { ctx.results.push(ctx.count * 10) return ctx }) .do("processAll", ctx => { ctx.results.push(ctx.count) return ctx }) ) }}// Results: [1, 3, 5, 70, 7, 90, 9]Walking through the result:
1, 3, 5— odd numbers ≤ 5: onlyprocessAllpushes them.7, 9— odd numbers > 5:processLargepushescount * 10first, thenprocessAllpushescount.- Even numbers are skipped by
CONTINUE. - The loop exits via
BREAKoncecount > 10.
- Return the context from operations inside nested builders (
return ctx) so the updated state flows to the next activity. - Keep predicates pure. They should only read the context and return a boolean — side effects belong in operations.
- For parallel fan-out rather than sequential looping, reach for SPLIT instead.