@gesslar/actioneer 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Actioneer
2
2
 
3
- Actioneer is a small, focused action orchestration library for Node.js and browser environments. It provides a fluent builder for composing activities and a concurrent runner with lifecycle hooks and simple loop semantics (while/until). The project is written as ES modules and targets Node 20+ and modern browsers.
3
+ Actioneer is a small, focused action orchestration library for Node.js and browser environments. It provides a fluent builder for composing activities and a concurrent runner with lifecycle hooks and control flow semantics (while/until/if/break/continue). The project is written as ES modules and targets Node 20+ and modern browsers.
4
4
 
5
5
  This repository extracts the action orchestration pieces from a larger codebase and exposes a compact API for building pipelines of work that can run concurrently with hook support and nested pipelines.
6
6
 
@@ -16,7 +16,7 @@ These classes work in browsers, Node.js, and browser-like environments such as T
16
16
  | ActionHooks | Lifecycle hook management (requires pre-instantiated hooks in browser) |
17
17
  | ActionRunner | Concurrent pipeline executor with configurable concurrency |
18
18
  | ActionWrapper | Activity container and iterator |
19
- | Activity | Activity definitions with WHILE, UNTIL, and SPLIT modes |
19
+ | Activity | Activity definitions with WHILE, UNTIL, IF, BREAK, CONTINUE, and SPLIT modes |
20
20
  | Piper | Base concurrent processing with worker pools |
21
21
 
22
22
  ### Node.js
@@ -133,7 +133,7 @@ If you'd like more complete typings or additional JSDoc, open an issue or send a
133
133
 
134
134
  ## Activity Modes
135
135
 
136
- Actioneer supports four distinct execution modes for activities, allowing you to control how operations are executed:
136
+ Actioneer supports six distinct execution modes for activities, allowing you to control how operations are executed:
137
137
 
138
138
  ### Execute Once (Default)
139
139
 
@@ -203,6 +203,133 @@ class ProcessorAction {
203
203
 
204
204
  The activity executes at least once, then continues while the predicate returns `false`. Once it returns `true`, execution moves to the next activity.
205
205
 
206
+ ### IF Mode
207
+
208
+ Conditionally executes an activity based on a predicate. Unlike WHILE/UNTIL, IF executes at most once:
209
+
210
+ ```js
211
+ import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
212
+
213
+ class ConditionalAction {
214
+ #shouldProcess = (ctx) => ctx.value > 10
215
+
216
+ #processLargeValue = (ctx) => {
217
+ ctx.processed = ctx.value * 2
218
+ }
219
+
220
+ setup(builder) {
221
+ builder
222
+ .do("initialize", ctx => { ctx.value = 15 })
223
+ .do("maybeProcess", ACTIVITY.IF, this.#shouldProcess, this.#processLargeValue)
224
+ .do("finish", ctx => { return ctx })
225
+ }
226
+ }
227
+ ```
228
+
229
+ If the predicate returns `true`, the activity executes once. If `false`, the activity is skipped entirely and execution moves to the next activity.
230
+
231
+ ### BREAK Mode
232
+
233
+ Breaks out of a WHILE or UNTIL loop when a predicate returns `true`. BREAK must be used inside a nested ActionBuilder within a loop:
234
+
235
+ ```js
236
+ import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
237
+
238
+ class BreakExample {
239
+ setup(builder) {
240
+ builder
241
+ .do("initialize", ctx => {
242
+ ctx.count = 0
243
+ ctx.items = []
244
+ })
245
+ .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100,
246
+ new ActionBuilder()
247
+ .do("increment", ctx => {
248
+ ctx.count++
249
+ ctx.items.push(ctx.count)
250
+ return ctx
251
+ })
252
+ .do("earlyExit", ACTIVITY.BREAK, ctx => ctx.count >= 5)
253
+ )
254
+ .do("finish", ctx => { return ctx.items }) // Returns [1, 2, 3, 4, 5]
255
+ }
256
+ }
257
+ ```
258
+
259
+ When the BREAK predicate returns `true`, the loop terminates immediately and execution continues with the next activity after the loop.
260
+
261
+ **Important:** BREAK only works inside a nested ActionBuilder that is the operation of a WHILE or UNTIL activity. Using BREAK outside of a loop context will throw an error.
262
+
263
+ ### CONTINUE Mode
264
+
265
+ Skips the remaining activities in the current loop iteration and continues to the next iteration. Like BREAK, CONTINUE must be used inside a nested ActionBuilder within a loop:
266
+
267
+ ```js
268
+ import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
269
+
270
+ class ContinueExample {
271
+ setup(builder) {
272
+ builder
273
+ .do("initialize", ctx => {
274
+ ctx.count = 0
275
+ ctx.processed = []
276
+ })
277
+ .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 5,
278
+ new ActionBuilder()
279
+ .do("increment", ctx => {
280
+ ctx.count++
281
+ return ctx
282
+ })
283
+ .do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0)
284
+ .do("process", ctx => {
285
+ ctx.processed.push(ctx.count)
286
+ return ctx
287
+ })
288
+ )
289
+ .do("finish", ctx => { return ctx.processed }) // Returns [1, 3, 5]
290
+ }
291
+ }
292
+ ```
293
+
294
+ When the CONTINUE predicate returns `true`, the remaining activities in that iteration are skipped, and the loop continues with its next iteration (re-evaluating the loop predicate for WHILE, or executing the operation then evaluating for UNTIL).
295
+
296
+ **Important:** Like BREAK, CONTINUE only works inside a nested ActionBuilder within a WHILE or UNTIL loop.
297
+
298
+ ### Combining Control Flow
299
+
300
+ You can combine IF, BREAK, and CONTINUE within the same loop for complex control flow:
301
+
302
+ ```js
303
+ class CombinedExample {
304
+ setup(builder) {
305
+ builder
306
+ .do("initialize", ctx => {
307
+ ctx.count = 0
308
+ ctx.results = []
309
+ })
310
+ .do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100,
311
+ new ActionBuilder()
312
+ .do("increment", ctx => { ctx.count++; return ctx })
313
+ .do("exitAt10", ACTIVITY.BREAK, ctx => ctx.count > 10)
314
+ .do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0)
315
+ .do("processLarge", ACTIVITY.IF, ctx => ctx.count > 5, ctx => {
316
+ ctx.results.push(ctx.count * 10)
317
+ return ctx
318
+ })
319
+ .do("processAll", ctx => {
320
+ ctx.results.push(ctx.count)
321
+ return ctx
322
+ })
323
+ )
324
+ }
325
+ }
326
+ // Results: [1, 3, 5, 70, 7, 90, 9]
327
+ // - 1, 3, 5: odd numbers <= 5, just pushed
328
+ // - 7, 9: odd numbers > 5, pushed with *10 first, then pushed
329
+ // - evens skipped by CONTINUE
330
+ // - loop exits when count > 10
331
+ ```
332
+
206
333
  ### SPLIT Mode
207
334
 
208
335
  Executes with a split/rejoin pattern for parallel execution. This mode requires a splitter function to divide the context and a rejoiner function to recombine results:
@@ -301,18 +428,22 @@ class NestedParallel {
301
428
 
302
429
  ### Mode Constraints
303
430
 
304
- - **Only one mode per activity**: You cannot combine WHILE, UNTIL, and SPLIT. Attempting to use multiple modes will throw an error: `"You can't combine activity kinds. Pick one: WHILE, UNTIL, or SPLIT!"`
431
+ - **Only one mode per activity**: Each activity can have only one mode. Attempting to combine modes will throw an error
305
432
  - **SPLIT requires both functions**: The splitter and rejoiner are both mandatory for SPLIT mode
306
- - **Predicates must return boolean**: For WHILE and UNTIL modes, predicates should return `true` or `false`
433
+ - **Predicates must return boolean**: All predicates (WHILE, UNTIL, IF, BREAK, CONTINUE) should return `true` or `false`
434
+ - **BREAK/CONTINUE require loop context**: These modes only work inside a nested ActionBuilder within a WHILE or UNTIL loop
307
435
 
308
436
  ### Mode Summary Table
309
437
 
310
- | Mode | Signature | Predicate Timing | Use Case |
311
- | ----------- | ---------------------------------------------------------- | ---------------- | ------------------------------------ |
312
- | **Default** | `.do(name, operation)` | N/A | Execute once per context |
313
- | **WHILE** | `.do(name, ACTIVITY.WHILE, predicate, operation)` | Before iteration | Loop while condition is true |
314
- | **UNTIL** | `.do(name, ACTIVITY.UNTIL, predicate, operation)` | After iteration | Loop until condition is true |
315
- | **SPLIT** | `.do(name, ACTIVITY.SPLIT, splitter, rejoiner, operation)` | N/A | Parallel execution with split/rejoin |
438
+ | Mode | Signature | Predicate Timing | Use Case |
439
+ | ------------ | ---------------------------------------------------------- | ---------------- | ------------------------------------------- |
440
+ | **Default** | `.do(name, operation)` | N/A | Execute once per context |
441
+ | **WHILE** | `.do(name, ACTIVITY.WHILE, predicate, operation)` | Before iteration | Loop while condition is true |
442
+ | **UNTIL** | `.do(name, ACTIVITY.UNTIL, predicate, operation)` | After iteration | Loop until condition is true |
443
+ | **IF** | `.do(name, ACTIVITY.IF, predicate, operation)` | Before execution | Conditional execution (once or skip) |
444
+ | **BREAK** | `.do(name, ACTIVITY.BREAK, predicate)` | When reached | Exit enclosing WHILE/UNTIL loop |
445
+ | **CONTINUE** | `.do(name, ACTIVITY.CONTINUE, predicate)` | When reached | Skip to next iteration of enclosing loop |
446
+ | **SPLIT** | `.do(name, ACTIVITY.SPLIT, splitter, rejoiner, operation)` | N/A | Parallel execution with split/rejoin |
316
447
 
317
448
  ## Running Actions: `run()` vs `pipe()`
318
449
 
@@ -376,6 +507,74 @@ The `pipe()` method uses `Promise.allSettled()` internally and returns an array
376
507
 
377
508
  This design ensures error handling responsibility stays at the call site - you decide how to handle failures rather than the framework deciding for you.
378
509
 
510
+ ## Pipeline Completion: `done()`
511
+
512
+ The `done()` method registers a callback that executes after all activities in the pipeline complete, regardless of whether an error occurred. This is useful for cleanup, finalization, or returning a transformed result.
513
+
514
+ ```js
515
+ import { ActionBuilder, ActionRunner } from "@gesslar/actioneer"
516
+
517
+ class MyAction {
518
+ setup(builder) {
519
+ builder
520
+ .do("step1", ctx => { ctx.a = 1 })
521
+ .do("step2", ctx => { ctx.b = 2 })
522
+ .done(ctx => {
523
+ // This runs after all activities complete
524
+ return { total: ctx.a + ctx.b }
525
+ })
526
+ }
527
+ }
528
+
529
+ const builder = new ActionBuilder(new MyAction())
530
+ const runner = new ActionRunner(builder)
531
+ const result = await runner.run({})
532
+ console.log(result) // { total: 3 }
533
+ ```
534
+
535
+ ### Key Behaviors
536
+
537
+ - **Always executes**: The `done()` callback runs even if an earlier activity throws an error (similar to `finally` in try/catch)
538
+ - **Top-level only**: The callback only runs for the outermost pipeline, not for nested builders inside loops (WHILE/UNTIL). However, for SPLIT activities, `done()` runs for each split context since each is an independent execution
539
+ - **Transform the result**: Whatever you return from `done()` becomes the final pipeline result
540
+ - **Access to action context**: The callback is bound to the action instance, so `this` refers to your action class
541
+ - **Async support**: The callback can be async and return a Promise
542
+
543
+ ### Use Cases
544
+
545
+ **Cleanup resources:**
546
+
547
+ ```js
548
+ builder
549
+ .do("openConnection", ctx => { ctx.conn = openDb() })
550
+ .do("query", ctx => { ctx.data = ctx.conn.query("SELECT *") })
551
+ .done(ctx => {
552
+ ctx.conn.close() // Always close, even on error
553
+ return ctx.data
554
+ })
555
+ ```
556
+
557
+ **Transform the final result:**
558
+
559
+ ```js
560
+ builder
561
+ .do("gather", ctx => { ctx.items = [1, 2, 3] })
562
+ .do("process", ctx => { ctx.items = ctx.items.map(x => x * 2) })
563
+ .done(ctx => ctx.items) // Return just the items array, not the whole context
564
+ ```
565
+
566
+ **Logging and metrics:**
567
+
568
+ ```js
569
+ builder
570
+ .do("start", ctx => { ctx.startTime = Date.now() })
571
+ .do("work", ctx => { /* ... */ })
572
+ .done(ctx => {
573
+ console.log(`Pipeline completed in ${Date.now() - ctx.startTime}ms`)
574
+ return ctx
575
+ })
576
+ ```
577
+
379
578
  ## ActionHooks
380
579
 
381
580
  Actioneer supports lifecycle hooks that can execute before and after each activity in your pipeline. Hooks can be configured by file path (Node.js only) or by providing a pre-instantiated hooks object (Node.js and browser).
@@ -558,9 +757,9 @@ Run the comprehensive test suite with Node's built-in test runner:
558
757
  npm test
559
758
  ```
560
759
 
561
- The test suite includes 150+ tests covering all core classes and behaviors:
760
+ The test suite includes 200+ tests covering all core classes and behaviors:
562
761
 
563
- - **Activity** - Activity definitions, ACTIVITY flags (WHILE, UNTIL, SPLIT), and execution
762
+ - **Activity** - Activity definitions, ACTIVITY flags (WHILE, UNTIL, IF, BREAK, CONTINUE, SPLIT), and execution
564
763
  - **ActionBuilder** - Fluent builder API, activity registration, and hooks configuration
565
764
  - **ActionWrapper** - Activity iteration and integration with ActionBuilder
566
765
  - **ActionRunner** - Pipeline execution, loop semantics, nested builders, and error handling
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "gesslar",
6
6
  "url": "https://gesslar.dev"
7
7
  },
8
- "version": "2.1.0",
8
+ "version": "2.2.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -49,10 +49,10 @@
49
49
  "node": ">=22"
50
50
  },
51
51
  "dependencies": {
52
- "@gesslar/toolkit": "^3.23.0"
52
+ "@gesslar/toolkit": "^3.24.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@gesslar/uglier": "^1.1.0",
55
+ "@gesslar/uglier": "^1.2.0",
56
56
  "eslint": "^9.39.2",
57
57
  "typescript": "^5.9.3"
58
58
  },
@@ -66,9 +66,13 @@ export default class ActionBuilder {
66
66
  #debug = null
67
67
  /** @type {symbol|null} */
68
68
  #tag = null
69
+ /** @type {string|null} */
69
70
  #hooksFile = null
71
+ /** @type {string|null} */
70
72
  #hooksKind = null
73
+ /** @type {import("./ActionHooks.js").default|null} */
71
74
  #hooks = null
75
+ /** @type {ActionFunction|null} */
72
76
  #done = null
73
77
 
74
78
  /**
@@ -100,9 +104,10 @@ export default class ActionBuilder {
100
104
  * Register an activity that the runner can execute.
101
105
  *
102
106
  * Overloads:
103
- * - do(name, op)
104
- * - do(name, kind, pred, opOrWrapper)
105
- * - do(name, kind, splitter, rejoiner, opOrWrapper)
107
+ * - do(name, op) - Simple once-off activity
108
+ * - do(name, kind, pred) - BREAK/CONTINUE control flow (no op, just predicate)
109
+ * - do(name, kind, pred, opOrWrapper) - WHILE/UNTIL/IF with predicate and operation
110
+ * - do(name, kind, splitter, rejoiner, opOrWrapper) - SPLIT with parallel execution
106
111
  *
107
112
  * @overload
108
113
  * @param {string|symbol} name Activity name
@@ -113,9 +118,17 @@ export default class ActionBuilder {
113
118
  /**
114
119
  * @overload
115
120
  * @param {string|symbol} name Activity name
116
- * @param {number} kind Kind bitfield from {@link ActivityFlags}.
121
+ * @param {number} kind ACTIVITY.BREAK or ACTIVITY.CONTINUE flag.
122
+ * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate to evaluate for control flow.
123
+ * @returns {ActionBuilder}
124
+ */
125
+
126
+ /**
127
+ * @overload
128
+ * @param {string|symbol} name Activity name
129
+ * @param {number} kind Activity kind (WHILE, UNTIL, or IF) from {@link ActivityFlags}.
117
130
  * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate executed before/after the op.
118
- * @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
131
+ * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
119
132
  * @returns {ActionBuilder}
120
133
  */
121
134
 
@@ -125,7 +138,7 @@ export default class ActionBuilder {
125
138
  * @param {number} kind ACTIVITY.SPLIT flag.
126
139
  * @param {(context: unknown) => unknown} splitter Splitter function for SPLIT mode.
127
140
  * @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Rejoiner function for SPLIT mode.
128
- * @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
141
+ * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
129
142
  * @returns {ActionBuilder}
130
143
  */
131
144
 
@@ -142,6 +155,7 @@ export default class ActionBuilder {
142
155
  // signatures
143
156
  // name, [function] => once
144
157
  // name, [number,function,function] => some kind of control operation (WHILE/UNTIL)
158
+ // name, [number,function] => some kind of control operation (BREAK/CONTINUE)
145
159
  // name, [number,function,function,function] => SPLIT operation with splitter/rejoiner
146
160
  // name, [number,function,ActionBuilder] => some kind of branch
147
161
 
@@ -155,6 +169,13 @@ export default class ActionBuilder {
155
169
  Valid.type(op, "Function")
156
170
 
157
171
  Object.assign(activityDefinition, {op, kind})
172
+ } else if(args.length === 2) {
173
+ const [kind, pred] = args
174
+ Valid.type(kind, "Number")
175
+ Valid.type(pred, "Function")
176
+ Valid.assert(kind === ACTIVITY.BREAK || kind === ACTIVITY.CONTINUE, "Invalid arguments for BREAK/CONTINUE.")
177
+
178
+ Object.assign(activityDefinition, {kind, pred})
158
179
  } else if(args.length === 3) {
159
180
  const [kind, pred, op] = args
160
181
 
@@ -172,7 +193,7 @@ export default class ActionBuilder {
172
193
  Valid.type(op, "Function|ActionBuilder")
173
194
 
174
195
  // Validate that kind is SPLIT
175
- if((kind & ACTIVITY.SPLIT) !== ACTIVITY.SPLIT)
196
+ if(kind !== ACTIVITY.SPLIT)
176
197
  throw Sass.new("4-argument form of 'do' is only valid for ACTIVITY.SPLIT")
177
198
 
178
199
  Object.assign(activityDefinition, {kind, splitter, rejoiner, op})
@@ -1,4 +1,4 @@
1
- import {Promised, Data, Sass, Valid} from "@gesslar/toolkit"
1
+ import {Promised, Data, Sass, Valid, Notify} from "@gesslar/toolkit"
2
2
 
3
3
  import {ACTIVITY} from "./Activity.js"
4
4
  import Piper from "./Piper.js"
@@ -36,6 +36,13 @@ export default class ActionRunner extends Piper {
36
36
  */
37
37
  #debug = () => {}
38
38
 
39
+ /**
40
+ * Event emitter for cross-runner communication (BREAK/CONTINUE signals).
41
+ *
42
+ * @type {typeof Notify}
43
+ */
44
+ #notify = Notify
45
+
39
46
  /**
40
47
  * Instantiate a runner over an optional action wrapper.
41
48
  *
@@ -63,24 +70,29 @@ export default class ActionRunner extends Piper {
63
70
  /**
64
71
  * Executes the configured action pipeline.
65
72
  * Builds the ActionWrapper on first run and caches it for subsequent calls.
66
- * Supports WHILE, UNTIL, and SPLIT activity kinds.
73
+ * Supports WHILE, UNTIL, IF, SPLIT, BREAK, and CONTINUE activity kinds.
67
74
  *
68
75
  * @param {unknown} context - Seed value passed to the first activity.
76
+ * @param {import("./ActionWrapper.js").default|null} [parentWrapper] - Parent wrapper for BREAK/CONTINUE signaling.
69
77
  * @returns {Promise<unknown>} Final value produced by the pipeline.
70
78
  * @throws {Sass} When no activities are registered, conflicting activity kinds are used, or execution fails.
71
79
  */
72
- async run(context) {
80
+ async run(context, parentWrapper=null) {
73
81
  if(!this.#actionWrapper)
74
82
  this.#actionWrapper = await this.#actionBuilder.build()
75
83
 
76
84
  const actionWrapper = this.#actionWrapper
77
- const activities = actionWrapper.activities
85
+ const activities = Array.from(actionWrapper.activities)
78
86
 
79
87
  try {
80
- for(const activity of activities) {
81
- try {
82
- // await timeout(500)
88
+ for(
89
+ let cursor = 0, max = activities.length;
90
+ cursor < max && cursor !== -1;
91
+ cursor++
92
+ ) {
93
+ const activity = activities[cursor]
83
94
 
95
+ try {
84
96
  const kind = activity.kind
85
97
 
86
98
  // If we have no kind, then it's just a once.
@@ -88,33 +100,50 @@ export default class ActionRunner extends Piper {
88
100
  if(!kind) {
89
101
  context = await this.#execute(activity, context)
90
102
  } else {
91
- // Validate that only one activity kind bit is set
92
- // (kind & (kind - 1)) !== 0 means multiple bits are set
93
- const multipleBitsSet = (kind & (kind - 1)) !== 0
94
- if(multipleBitsSet)
95
- throw Sass.new(
96
- "For Kathy Griffin's sake! You can't combine activity kinds. " +
97
- "Pick one: WHILE, UNTIL, or SPLIT!"
98
- )
99
-
100
- const {WHILE,UNTIL,SPLIT} = ACTIVITY
101
- const kindWhile = kind & WHILE
102
- const kindUntil = kind & UNTIL
103
- const kindSplit = kind & SPLIT
104
-
105
- if(kindWhile || kindUntil) {
106
- const predicate = activity.pred
107
-
103
+ const {UNTIL, WHILE, IF, SPLIT, BREAK, CONTINUE} = ACTIVITY
104
+ const kindUntil = kind === UNTIL
105
+ const kindWhile = kind === WHILE
106
+ const kindIf = kind === IF
107
+ const kindSplit = kind === SPLIT
108
+ const kindBreak = kind === BREAK
109
+ const kindContinue = kind === CONTINUE
110
+
111
+ if(kindBreak || kindContinue) {
112
+ if(!parentWrapper)
113
+ throw Sass.new(`Invalid use of control flow outside of context.`)
114
+
115
+ if(await this.#evalPredicate(activity, context)) {
116
+ if(kindBreak) {
117
+ this.#notify.emit("loop.break", parentWrapper)
118
+ break
119
+ }
120
+
121
+ if(kindContinue)
122
+ cursor = max
123
+ }
124
+ } else if(kindIf) {
125
+ if(await this.#evalPredicate(activity, context))
126
+ context = await this.#execute(activity, context)
127
+ } else if(kindWhile || kindUntil) {
128
+ // Simple if, no loop, only gainz.
108
129
  for(;;) {
109
-
110
130
  if(kindWhile)
111
- if(!await this.#hasPredicate(activity,predicate,context))
131
+ if(!await this.#evalPredicate(activity, context))
112
132
  break
113
133
 
114
- context = await this.#execute(activity,context)
134
+ let weWereOnABreak = false
135
+ const breakReceiver = this.#notify.on("loop.break", wrapper => {
136
+ if(wrapper.id === actionWrapper.id) {
137
+ weWereOnABreak = true
138
+ }
139
+ })
140
+ context = await this.#execute(activity, context)
141
+ breakReceiver()
142
+ if(weWereOnABreak)
143
+ break
115
144
 
116
145
  if(kindUntil)
117
- if(await this.#hasPredicate(activity,predicate,context))
146
+ if(await this.#evalPredicate(activity, context))
118
147
  break
119
148
  }
120
149
  } else if(kindSplit) {
@@ -130,8 +159,10 @@ export default class ActionRunner extends Piper {
130
159
 
131
160
  const original = context
132
161
  const splitContexts = await splitter.call(
133
- activity.action, context
162
+ activity.action,
163
+ context
134
164
  )
165
+
135
166
  let settled
136
167
 
137
168
  if(activity.opKind === "ActionBuilder") {
@@ -145,7 +176,6 @@ export default class ActionRunner extends Piper {
145
176
  debug: this.#debug, name: activity.name
146
177
  })
147
178
 
148
- // pipe() returns settled results with concurrency control
149
179
  settled = await runner.pipe(splitContexts)
150
180
  } else {
151
181
  // For plain functions, process each split context
@@ -171,7 +201,8 @@ export default class ActionRunner extends Piper {
171
201
  }
172
202
  } finally {
173
203
  // Execute done callback if registered - always runs, even on error
174
- if(actionWrapper.done) {
204
+ // Only run for top-level pipelines, not nested builders (inside loops)
205
+ if(actionWrapper.done && !parentWrapper) {
175
206
  try {
176
207
  context = await actionWrapper.done.call(
177
208
  actionWrapper.action, context
@@ -220,11 +251,11 @@ export default class ActionRunner extends Piper {
220
251
  if(parallel) {
221
252
  return await runner.pipe(context)
222
253
  } else {
223
- return await runner.run(context)
254
+ return await runner.run(context, activity.wrapper)
224
255
  }
225
256
  } else if(opKind === "Function") {
226
257
  try {
227
- const result = await activity.run(context)
258
+ const result = await activity.run(context, activity.wrapper)
228
259
 
229
260
  if(Data.isType(result, "ActionBuilder")) {
230
261
  if(activity.action)
@@ -240,7 +271,7 @@ export default class ActionRunner extends Piper {
240
271
  if(parallel) {
241
272
  return await runner.pipe(context)
242
273
  } else {
243
- return await runner.run(context)
274
+ return await runner.run(context, activity.wrapper)
244
275
  }
245
276
  } else {
246
277
  return result
@@ -256,18 +287,17 @@ export default class ActionRunner extends Piper {
256
287
  }
257
288
 
258
289
  /**
259
- * Evaluate the predicate for WHILE/UNTIL activity kinds.
290
+ * Evaluate the predicate for WHILE/UNTIL/IF/BREAK/CONTINUE activity kinds.
260
291
  *
261
292
  * @param {import("./Activity.js").default} activity Activity currently executing.
262
- * @param {(context: unknown) => boolean|Promise<boolean>} predicate Predicate to evaluate.
263
293
  * @param {unknown} context Current pipeline context.
264
- * @returns {Promise<boolean>} True when the predicate allows another iteration.
294
+ * @returns {Promise<boolean>} True when the predicate condition is met.
265
295
  * @private
266
296
  */
267
- async #hasPredicate(activity,predicate,context) {
268
- Valid.type(predicate, "Function")
297
+ async #evalPredicate(activity, context) {
298
+ Valid.type(activity?.pred, "Function")
269
299
 
270
- return !!(await predicate.call(activity.action, context))
300
+ return !!(await activity.pred.call(activity.action, context))
271
301
  }
272
302
 
273
303
  toString() {
@@ -24,6 +24,7 @@ export default class ActionWrapper {
24
24
  * @type {Map<string|symbol, WrappedActivityConfig>}
25
25
  */
26
26
  #activities = new Map()
27
+
27
28
  /**
28
29
  * Logger invoked for wrapper lifecycle events.
29
30
  *
@@ -31,11 +32,14 @@ export default class ActionWrapper {
31
32
  */
32
33
  #debug = () => {}
33
34
 
35
+ /** @type {import("./ActionHooks.js").default|null} */
34
36
  #hooks = null
35
-
37
+ /** @type {((context: unknown) => unknown|Promise<unknown>)|null} */
36
38
  #done = null
37
-
39
+ /** @type {unknown} */
38
40
  #action = null
41
+ /** @type {symbol} */
42
+ #id = Symbol(performance.now())
39
43
 
40
44
  /**
41
45
  * Create a wrapper from the builder payload.
@@ -47,7 +51,18 @@ export default class ActionWrapper {
47
51
  this.#hooks = hooks
48
52
  this.#done = doneCallback
49
53
  this.#action = action
50
- this.#activities = activities
54
+
55
+ for(const [key, value] of activities) {
56
+ this.#activities.set(
57
+ key,
58
+ new Activity({
59
+ ...value,
60
+ hooks: this.#hooks,
61
+ wrapper: this
62
+ })
63
+ )
64
+ }
65
+
51
66
  this.#debug(
52
67
  "Instantiating ActionWrapper with %o activities.",
53
68
  2,
@@ -55,18 +70,23 @@ export default class ActionWrapper {
55
70
  )
56
71
  }
57
72
 
58
- *#_activities() {
59
- for(const [,activity] of this.#activities)
60
- yield new Activity({...activity, hooks: this.#hooks})
73
+ /**
74
+ * Unique identifier for this wrapper instance.
75
+ * Used by BREAK/CONTINUE to match events to the correct loop.
76
+ *
77
+ * @returns {symbol} Unique symbol identifier
78
+ */
79
+ get id() {
80
+ return this.#id
61
81
  }
62
82
 
63
83
  /**
64
84
  * Iterator over the registered activities.
65
85
  *
66
- * @returns {ActivityIterator} Lazy iterator yielding Activity instances.
86
+ * @returns {Iterator<Activity>} Iterator yielding Activity instances.
67
87
  */
68
88
  get activities() {
69
- return this.#_activities()
89
+ return this.#activities.values()
70
90
  }
71
91
 
72
92
  /**
@@ -8,14 +8,20 @@ import {Data} from "@gesslar/toolkit"
8
8
  *
9
9
  * @readonly
10
10
  * @enum {number}
11
- * @property {number} WHILE - Execute activity while predicate returns true (2)
12
- * @property {number} UNTIL - Execute activity until predicate returns true (4)
13
- * @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution (8)
11
+ * @property {number} WHILE - Execute activity while predicate returns true 1
12
+ * @property {number} UNTIL - Execute activity until predicate returns true 2
13
+ * @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution 3
14
+ * @property {number} IF - Execute activity if predicate returns true 4
15
+ * @property {number} BREAK - Break out of a WHILE/UNTIL if predicate returns true 5
16
+ * @property {number} CONTINUE - Returns to the top of a WHILE/UNTIL if predicate returns true 6
14
17
  */
15
18
  export const ACTIVITY = Object.freeze({
16
- WHILE: 1<<1,
17
- UNTIL: 1<<2,
18
- SPLIT: 1<<3,
19
+ WHILE: 1,
20
+ UNTIL: 2,
21
+ SPLIT: 3,
22
+ IF: 4,
23
+ BREAK: 5,
24
+ CONTINUE: 6,
19
25
  })
20
26
 
21
27
  export default class Activity {
@@ -37,6 +43,10 @@ export default class Activity {
37
43
  #rejoiner = null
38
44
  /** @type {((context: unknown) => unknown)|null} */
39
45
  #splitter = null
46
+ /** @type {import("./ActionWrapper.js").default|null} */
47
+ #wrapper = null
48
+ /** @type {symbol} */
49
+ #id = Symbol(performance.now())
40
50
 
41
51
  /**
42
52
  * Construct an Activity definition wrapper.
@@ -50,8 +60,9 @@ export default class Activity {
50
60
  * @param {ActionHooks} [init.hooks] - Optional hooks instance
51
61
  * @param {(context: unknown) => unknown} [init.splitter] - Optional splitter function for SPLIT activities
52
62
  * @param {(originalContext: unknown, splitResults: unknown) => unknown} [init.rejoiner] - Optional rejoiner function for SPLIT activities
63
+ * @param {import("./ActionWrapper.js").default} [init.wrapper] - Optional wrapper containing this activity
53
64
  */
54
- constructor({action,name,op,kind,pred,hooks,splitter,rejoiner}) {
65
+ constructor({action,name,op,kind,pred,hooks,splitter,rejoiner,wrapper}) {
55
66
  this.#action = action
56
67
  this.#hooks = hooks
57
68
  this.#kind = kind
@@ -60,6 +71,16 @@ export default class Activity {
60
71
  this.#pred = pred
61
72
  this.#rejoiner = rejoiner
62
73
  this.#splitter = splitter
74
+ this.#wrapper = wrapper ?? null
75
+ }
76
+
77
+ /**
78
+ * Unique identifier for this activity instance.
79
+ *
80
+ * @returns {symbol} Unique symbol identifier
81
+ */
82
+ get id() {
83
+ return this.#id
63
84
  }
64
85
 
65
86
  /**
@@ -81,7 +102,7 @@ export default class Activity {
81
102
  }
82
103
 
83
104
  /**
84
- * The predicate function for WHILE/UNTIL flows.
105
+ * The predicate function for WHILE/UNTIL/IF flows.
85
106
  *
86
107
  * @returns {(context: unknown) => boolean|Promise<boolean>|undefined} - Predicate used to continue/stop loops
87
108
  */
@@ -143,6 +164,16 @@ export default class Activity {
143
164
  return this.#action
144
165
  }
145
166
 
167
+ /**
168
+ * Get the ActionWrapper containing this activity.
169
+ * Used by BREAK/CONTINUE to signal the parent loop.
170
+ *
171
+ * @returns {import("./ActionWrapper.js").default|null} The wrapper or null
172
+ */
173
+ get wrapper() {
174
+ return this.#wrapper ?? null
175
+ }
176
+
146
177
  /**
147
178
  * Execute the activity with before/after hooks.
148
179
  *
@@ -58,9 +58,10 @@ export default class ActionBuilder {
58
58
  * Register an activity that the runner can execute.
59
59
  *
60
60
  * Overloads:
61
- * - do(name, op)
62
- * - do(name, kind, pred, opOrWrapper)
63
- * - do(name, kind, splitter, rejoiner, opOrWrapper)
61
+ * - do(name, op) - Simple once-off activity
62
+ * - do(name, kind, pred) - BREAK/CONTINUE control flow (no op, just predicate)
63
+ * - do(name, kind, pred, opOrWrapper) - WHILE/UNTIL/IF with predicate and operation
64
+ * - do(name, kind, splitter, rejoiner, opOrWrapper) - SPLIT with parallel execution
64
65
  *
65
66
  * @overload
66
67
  * @param {string|symbol} name Activity name
@@ -71,22 +72,30 @@ export default class ActionBuilder {
71
72
  /**
72
73
  * @overload
73
74
  * @param {string|symbol} name Activity name
74
- * @param {number} kind Kind bitfield from {@link ActivityFlags}.
75
+ * @param {number} kind ACTIVITY.BREAK or ACTIVITY.CONTINUE flag.
76
+ * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate to evaluate for control flow.
77
+ * @returns {ActionBuilder}
78
+ */
79
+ do(name: string | symbol, kind: number, pred: (context: unknown) => boolean | Promise<boolean>): ActionBuilder;
80
+ /**
81
+ * @overload
82
+ * @param {string|symbol} name Activity name
83
+ * @param {number} kind Activity kind (WHILE, UNTIL, or IF) from {@link ActivityFlags}.
75
84
  * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate executed before/after the op.
76
- * @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
85
+ * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
77
86
  * @returns {ActionBuilder}
78
87
  */
79
- do(name: string | symbol, kind: number, pred: (context: unknown) => boolean | Promise<boolean>, op: ActionFunction | import("./ActionWrapper.js").default): ActionBuilder;
88
+ do(name: string | symbol, kind: number, pred: (context: unknown) => boolean | Promise<boolean>, op: ActionFunction | ActionBuilder): ActionBuilder;
80
89
  /**
81
90
  * @overload
82
91
  * @param {string|symbol} name Activity name
83
92
  * @param {number} kind ACTIVITY.SPLIT flag.
84
93
  * @param {(context: unknown) => unknown} splitter Splitter function for SPLIT mode.
85
94
  * @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Rejoiner function for SPLIT mode.
86
- * @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
95
+ * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
87
96
  * @returns {ActionBuilder}
88
97
  */
89
- do(name: string | symbol, kind: number, splitter: (context: unknown) => unknown, rejoiner: (originalContext: unknown, splitResults: unknown) => unknown, op: ActionFunction | import("./ActionWrapper.js").default): ActionBuilder;
98
+ do(name: string | symbol, kind: number, splitter: (context: unknown) => unknown, rejoiner: (originalContext: unknown, splitResults: unknown) => unknown, op: ActionFunction | ActionBuilder): ActionBuilder;
90
99
  /**
91
100
  * Configure hooks to be loaded from a file when the action is built.
92
101
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionBuilder.js"],"names":[],"mappings":"AAMA,kEAAkE;AAClE,uEAAuE;AAEvE;;GAEG;AAEH;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAcE;;;;;OAKG;IACH,qBAHW,mBAAmB,mBACnB,mBAAmB,EAe7B;IAED,yBAEC;;;;;;;;;;;;;;IAUE,SACQ,MAAM,GAAC,MAAM,MACb,cAAc,GACZ,aAAa,CACzB;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,QACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,MAC9C,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO,GACjD,aAAa,CACzB;;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,YACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,YAC7B,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,MAC5D,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO,GACjD,aAAa,CACzB;IA0DD;;;;;;;OAOG;IACH,yBALW,MAAM,aACN,MAAM,GACJ,aAAa,CAYzB;IAED;;;;;;OAMG;IACH,iBAJW,OAAO,kBAAkB,EAAE,OAAO,GAChC,aAAa,CAgBzB;IAED;;;;;;OAMG;IACH,mBAHW,mBAAmB,GACjB,aAAa,CAczB;IAED;;;;;OAKG;IACH,eAHW,cAAc,GACZ,aAAa,CAOzB;IAeD;;;;;OAKG;IACH,SAFa,OAAO,CAAC,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAoBzD;;CAqBF;2BA1Ta,OAAO,mBAAmB,EAAE,OAAO;4BACnC,cAAc,eAAe,EAAE,QAAQ;sBAGxC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;WAKjE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;YAYhC,mBAAmB,GAAC,IAAI;;;;WACxB,OAAO,GAAC,IAAI;;;;UACZ,MAAM,GAAC,MAAM;;;;QACb,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO;;;;;;;;sBAEzC,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;6BAI/C,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC"}
1
+ {"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionBuilder.js"],"names":[],"mappings":"AAMA,kEAAkE;AAClE,uEAAuE;AAEvE;;GAEG;AAEH;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAkBE;;;;;OAKG;IACH,qBAHW,mBAAmB,mBACnB,mBAAmB,EAe7B;IAED,yBAEC;;;;;;;;;;;;;;;IAWE,SACQ,MAAM,GAAC,MAAM,MACb,cAAc,GACZ,aAAa,CACzB;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,QACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAC5C,aAAa,CACzB;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,QACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,MAC9C,cAAc,GAAC,aAAa,GAC1B,aAAa,CACzB;;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,YACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,YAC7B,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,MAC5D,cAAc,GAAC,aAAa,GAC1B,aAAa,CACzB;IAkED;;;;;;;OAOG;IACH,yBALW,MAAM,aACN,MAAM,GACJ,aAAa,CAYzB;IAED;;;;;;OAMG;IACH,iBAJW,OAAO,kBAAkB,EAAE,OAAO,GAChC,aAAa,CAgBzB;IAED;;;;;;OAMG;IACH,mBAHW,mBAAmB,GACjB,aAAa,CAczB;IAED;;;;;OAKG;IACH,eAHW,cAAc,GACZ,aAAa,CAOzB;IAeD;;;;;OAKG;IACH,SAFa,OAAO,CAAC,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAqBzD;;CAqBF;2BAhVa,OAAO,mBAAmB,EAAE,OAAO;4BACnC,cAAc,eAAe,EAAE,QAAQ;sBAGxC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;WAKjE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;YAYhC,mBAAmB,GAAC,IAAI;;;;WACxB,OAAO,GAAC,IAAI;;;;UACZ,MAAM,GAAC,MAAM;;;;QACb,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO;;;;;;;;sBAEzC,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;6BAI/C,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC"}
@@ -26,13 +26,14 @@ export default class ActionRunner extends Piper {
26
26
  /**
27
27
  * Executes the configured action pipeline.
28
28
  * Builds the ActionWrapper on first run and caches it for subsequent calls.
29
- * Supports WHILE, UNTIL, and SPLIT activity kinds.
29
+ * Supports WHILE, UNTIL, IF, SPLIT, BREAK, and CONTINUE activity kinds.
30
30
  *
31
31
  * @param {unknown} context - Seed value passed to the first activity.
32
+ * @param {import("./ActionWrapper.js").default|null} [parentWrapper] - Parent wrapper for BREAK/CONTINUE signaling.
32
33
  * @returns {Promise<unknown>} Final value produced by the pipeline.
33
34
  * @throws {Sass} When no activities are registered, conflicting activity kinds are used, or execution fails.
34
35
  */
35
- run(context: unknown): Promise<unknown>;
36
+ run(context: unknown, parentWrapper?: import("./ActionWrapper.js").default | null): Promise<unknown>;
36
37
  #private;
37
38
  }
38
39
  export type DebugFn = (message: string, level?: number, ...args: Array<unknown>) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionRunner.js"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;GAEG;AAEH;;;GAGG;AACH;;;;;;GAMG;AACH;IAaE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAkB7B;IAED;;;;;;;;OAQG;IACH,aAJW,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,CAgH5B;;CA0FF;sBAvQY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;4BAIlE,OAAO,oBAAoB,EAAE,OAAO;;;;;;;kBAP/B,YAAY"}
1
+ {"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionRunner.js"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;GAEG;AAEH;;;GAGG;AAEH;;;;;;GAMG;AACH;IAoBE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAkB7B;IAED;;;;;;;;;OASG;IACH,aALW,OAAO,kBACP,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,GACvC,OAAO,CAAC,OAAO,CAAC,CA4I5B;;CAyFF;sBA3SY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;4BAIlE,OAAO,oBAAoB,EAAE,OAAO;;;;;;;kBAP/B,YAAY"}
@@ -19,22 +19,35 @@ export default class ActionWrapper {
19
19
  *
20
20
  * @param {{activities: Map<string|symbol, WrappedActivityConfig>, debug: (message: string, level?: number, ...args: Array<unknown>) => void}} init Builder payload containing activities + logger.
21
21
  */
22
- constructor({ activities, hooks, debug, done: doneCallback }: {
22
+ constructor({ activities, hooks, debug, done: doneCallback, action }: {
23
23
  activities: Map<string | symbol, WrappedActivityConfig>;
24
24
  debug: (message: string, level?: number, ...args: Array<unknown>) => void;
25
25
  });
26
+ /**
27
+ * Unique identifier for this wrapper instance.
28
+ * Used by BREAK/CONTINUE to match events to the correct loop.
29
+ *
30
+ * @returns {symbol} Unique symbol identifier
31
+ */
32
+ get id(): symbol;
26
33
  /**
27
34
  * Iterator over the registered activities.
28
35
  *
29
- * @returns {ActivityIterator} Lazy iterator yielding Activity instances.
36
+ * @returns {Iterator<Activity>} Iterator yielding Activity instances.
30
37
  */
31
- get activities(): ActivityIterator;
38
+ get activities(): Iterator<Activity>;
32
39
  /**
33
40
  * Get the done callback if registered.
34
41
  *
35
42
  * @returns {((context: unknown) => unknown|Promise<unknown>)|null} Done callback or null.
36
43
  */
37
44
  get done(): ((context: unknown) => unknown | Promise<unknown>) | null;
45
+ /**
46
+ * Get the action instance.
47
+ *
48
+ * @returns {unknown|null} Action instance or null.
49
+ */
50
+ get action(): unknown | null;
38
51
  #private;
39
52
  }
40
53
  export type WrappedActivityConfig = {
@@ -1 +1 @@
1
- {"version":3,"file":"ActionWrapper.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionWrapper.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH;;GAEG;AAEH;;GAEG;AACH;IAkBE;;;;OAIG;IACH,8DAFW;QAAC,UAAU,EAAE,GAAG,CAAC,MAAM,GAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;KAAC,EAY5I;IAOD;;;;OAIG;IACH,kBAFa,gBAAgB,CAI5B;IAED;;;;OAIG;IACH,YAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAC,IAAI,CAIjE;;CACF;;;;;UAxEa,MAAM,GAAC,MAAM;;;;QACb,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,aAAa;;;;;;;;sBAElD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;;;aAC9C,OAAO;;;;uBACG,MAAM,UAAU,MAAM,WAAW,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;+BAInE,OAAO,kBAAkB,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC;qBAbrD,eAAe"}
1
+ {"version":3,"file":"ActionWrapper.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionWrapper.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH;;GAEG;AAEH;;GAEG;AACH;IAwBE;;;;OAIG;IACH,sEAFW;QAAC,UAAU,EAAE,GAAG,CAAC,MAAM,GAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;KAAC,EAwB5I;IAED;;;;;OAKG;IACH,UAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,kBAFa,QAAQ,CAAC,QAAQ,CAAC,CAI9B;IAED;;;;OAIG;IACH,YAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAC,IAAI,CAIjE;IAED;;;;OAIG;IACH,cAFa,OAAO,GAAC,IAAI,CAIxB;;CACF;;;;;UAxGa,MAAM,GAAC,MAAM;;;;QACb,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,aAAa;;;;;;;;sBAElD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;;;aAC9C,OAAO;;;;uBACG,MAAM,UAAU,MAAM,WAAW,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;+BAInE,OAAO,kBAAkB,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC;qBAbrD,eAAe"}
@@ -9,14 +9,20 @@ export type ACTIVITY = number;
9
9
  *
10
10
  * @readonly
11
11
  * @enum {number}
12
- * @property {number} WHILE - Execute activity while predicate returns true (2)
13
- * @property {number} UNTIL - Execute activity until predicate returns true (4)
14
- * @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution (8)
12
+ * @property {number} WHILE - Execute activity while predicate returns true 1
13
+ * @property {number} UNTIL - Execute activity until predicate returns true 2
14
+ * @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution 3
15
+ * @property {number} IF - Execute activity if predicate returns true 4
16
+ * @property {number} BREAK - Break out of a WHILE/UNTIL if predicate returns true 5
17
+ * @property {number} CONTINUE - Returns to the top of a WHILE/UNTIL if predicate returns true 6
15
18
  */
16
19
  export const ACTIVITY: Readonly<{
17
- WHILE: number;
18
- UNTIL: number;
19
- SPLIT: number;
20
+ WHILE: 1;
21
+ UNTIL: 2;
22
+ SPLIT: 3;
23
+ IF: 4;
24
+ BREAK: 5;
25
+ CONTINUE: 6;
20
26
  }>;
21
27
  export default class Activity {
22
28
  /**
@@ -31,8 +37,9 @@ export default class Activity {
31
37
  * @param {ActionHooks} [init.hooks] - Optional hooks instance
32
38
  * @param {(context: unknown) => unknown} [init.splitter] - Optional splitter function for SPLIT activities
33
39
  * @param {(originalContext: unknown, splitResults: unknown) => unknown} [init.rejoiner] - Optional rejoiner function for SPLIT activities
40
+ * @param {import("./ActionWrapper.js").default} [init.wrapper] - Optional wrapper containing this activity
34
41
  */
35
- constructor({ action, name, op, kind, pred, hooks, splitter, rejoiner }: {
42
+ constructor({ action, name, op, kind, pred, hooks, splitter, rejoiner, wrapper }: {
36
43
  action: unknown;
37
44
  name: string | symbol;
38
45
  op: (context: unknown) => unknown | Promise<unknown> | import("./ActionBuilder.js").default;
@@ -41,7 +48,14 @@ export default class Activity {
41
48
  hooks?: import("./ActionHooks.js").default | undefined;
42
49
  splitter?: ((context: unknown) => unknown) | undefined;
43
50
  rejoiner?: ((originalContext: unknown, splitResults: unknown) => unknown) | undefined;
51
+ wrapper?: import("./ActionWrapper.js").default | undefined;
44
52
  });
53
+ /**
54
+ * Unique identifier for this activity instance.
55
+ *
56
+ * @returns {symbol} Unique symbol identifier
57
+ */
58
+ get id(): symbol;
45
59
  /**
46
60
  * The activity name.
47
61
  *
@@ -55,7 +69,7 @@ export default class Activity {
55
69
  */
56
70
  get kind(): number | null;
57
71
  /**
58
- * The predicate function for WHILE/UNTIL flows.
72
+ * The predicate function for WHILE/UNTIL/IF flows.
59
73
  *
60
74
  * @returns {(context: unknown) => boolean|Promise<boolean>|undefined} - Predicate used to continue/stop loops
61
75
  */
@@ -96,6 +110,13 @@ export default class Activity {
96
110
  * @returns {unknown} - Bound action instance
97
111
  */
98
112
  get action(): unknown;
113
+ /**
114
+ * Get the ActionWrapper containing this activity.
115
+ * Used by BREAK/CONTINUE to signal the parent loop.
116
+ *
117
+ * @returns {import("./ActionWrapper.js").default|null} The wrapper or null
118
+ */
119
+ get wrapper(): import("./ActionWrapper.js").default | null;
99
120
  /**
100
121
  * Execute the activity with before/after hooks.
101
122
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Activity.d.ts","sourceRoot":"","sources":["../../../browser/lib/Activity.js"],"names":[],"mappings":";;;uBASU,MAAM;AAPhB,gEAAgE;AAEhE;;;;;;;;;GASG;AACH;;;;GAIE;AAEF;IAoBE;;;;;;;;;;;;OAYG;IACH,yEATG;QAAsB,MAAM,EAApB,OAAO;QACa,IAAI,EAAxB,MAAM,GAAC,MAAM;QAC6E,EAAE,EAA5F,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,oBAAoB,EAAE,OAAO;QACrE,IAAI;QACoC,IAAI,cAAhD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;QAC3B,KAAK;QACa,QAAQ,cAAnC,OAAO,KAAK,OAAO;QACuC,QAAQ,sBAA1D,OAAO,gBAAgB,OAAO,KAAK,OAAO;KACtE,EAUA;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,YAFa,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,YAFa,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,SAAS,CAIpE;IAED;;;;OAIG;IACH,eAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,UAFa,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,oBAAoB,EAAE,OAAO,CAI/F;IAED;;;;OAIG;IACH,gBAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,GAAC,IAAI,CAIhD;IAED;;;;OAIG;IACH,gBAFa,CAAC,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC,GAAC,IAAI,CAI/E;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;;OAKG;IACH,aAHW,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,CAa5B;IAED;;;;;OAKG;IACH,sBAHW,WAAW,GACT,IAAI,CAOhB;IAED;;;;OAIG;IACH,aAFa,WAAW,GAAC,IAAI,CAI5B;;CACF;0BAvLa,OAAO,kBAAkB,EAAE,OAAO"}
1
+ {"version":3,"file":"Activity.d.ts","sourceRoot":"","sources":["../../../browser/lib/Activity.js"],"names":[],"mappings":";;;uBASU,MAAM;AAPhB,gEAAgE;AAEhE;;;;;;;;;;;;GAYG;AACH;;;;;;;GAOE;AAEF;IAwBE;;;;;;;;;;;;;OAaG;IACH,kFAVG;QAAsB,MAAM,EAApB,OAAO;QACa,IAAI,EAAxB,MAAM,GAAC,MAAM;QAC6E,EAAE,EAA5F,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,oBAAoB,EAAE,OAAO;QACrE,IAAI;QACoC,IAAI,cAAhD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;QAC3B,KAAK;QACa,QAAQ,cAAnC,OAAO,KAAK,OAAO;QACuC,QAAQ,sBAA1D,OAAO,gBAAgB,OAAO,KAAK,OAAO;QAChB,OAAO;KAC7D,EAWA;IAED;;;;OAIG;IACH,UAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,YAFa,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,YAFa,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,SAAS,CAIpE;IAED;;;;OAIG;IACH,eAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,UAFa,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,oBAAoB,EAAE,OAAO,CAI/F;IAED;;;;OAIG;IACH,gBAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,GAAC,IAAI,CAIhD;IAED;;;;OAIG;IACH,gBAFa,CAAC,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC,GAAC,IAAI,CAI/E;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;;OAKG;IACH,eAFa,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,CAIrD;IAED;;;;;OAKG;IACH,aAHW,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,CAa5B;IAED;;;;;OAKG;IACH,sBAHW,WAAW,GACT,IAAI,CAOhB;IAED;;;;OAIG;IACH,aAFa,WAAW,GAAC,IAAI,CAI5B;;CACF;0BAtNa,OAAO,kBAAkB,EAAE,OAAO"}