@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 +212 -13
- package/package.json +3 -3
- package/src/browser/lib/ActionBuilder.js +28 -7
- package/src/browser/lib/ActionRunner.js +70 -40
- package/src/browser/lib/ActionWrapper.js +28 -8
- package/src/browser/lib/Activity.js +39 -8
- package/src/types/browser/lib/ActionBuilder.d.ts +17 -8
- package/src/types/browser/lib/ActionBuilder.d.ts.map +1 -1
- package/src/types/browser/lib/ActionRunner.d.ts +3 -2
- package/src/types/browser/lib/ActionRunner.d.ts.map +1 -1
- package/src/types/browser/lib/ActionWrapper.d.ts +16 -3
- package/src/types/browser/lib/ActionWrapper.d.ts.map +1 -1
- package/src/types/browser/lib/Activity.d.ts +29 -8
- package/src/types/browser/lib/Activity.d.ts.map +1 -1
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
|
|
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
|
|
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**:
|
|
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**:
|
|
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
|
|
311
|
-
|
|
|
312
|
-
| **Default**
|
|
313
|
-
| **WHILE**
|
|
314
|
-
| **UNTIL**
|
|
315
|
-
| **
|
|
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
|
|
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.
|
|
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.
|
|
52
|
+
"@gesslar/toolkit": "^3.24.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@gesslar/uglier": "^1.
|
|
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,
|
|
105
|
-
* - do(name, kind,
|
|
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
|
|
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|
|
|
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|
|
|
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(
|
|
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,
|
|
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(
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.#
|
|
131
|
+
if(!await this.#evalPredicate(activity, context))
|
|
112
132
|
break
|
|
113
133
|
|
|
114
|
-
|
|
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.#
|
|
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,
|
|
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
|
-
|
|
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
|
|
294
|
+
* @returns {Promise<boolean>} True when the predicate condition is met.
|
|
265
295
|
* @private
|
|
266
296
|
*/
|
|
267
|
-
async #
|
|
268
|
-
Valid.type(
|
|
297
|
+
async #evalPredicate(activity, context) {
|
|
298
|
+
Valid.type(activity?.pred, "Function")
|
|
269
299
|
|
|
270
|
-
return !!(await
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 {
|
|
86
|
+
* @returns {Iterator<Activity>} Iterator yielding Activity instances.
|
|
67
87
|
*/
|
|
68
88
|
get activities() {
|
|
69
|
-
return this.#
|
|
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
|
|
12
|
-
* @property {number} UNTIL - Execute activity until predicate returns true
|
|
13
|
-
* @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution
|
|
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
|
|
17
|
-
UNTIL:
|
|
18
|
-
SPLIT:
|
|
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,
|
|
63
|
-
* - do(name, kind,
|
|
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
|
|
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|
|
|
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 |
|
|
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|
|
|
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 |
|
|
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;
|
|
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,
|
|
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;
|
|
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 {
|
|
36
|
+
* @returns {Iterator<Activity>} Iterator yielding Activity instances.
|
|
30
37
|
*/
|
|
31
|
-
get activities():
|
|
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;
|
|
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
|
|
13
|
-
* @property {number} UNTIL - Execute activity until predicate returns true
|
|
14
|
-
* @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution
|
|
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:
|
|
18
|
-
UNTIL:
|
|
19
|
-
SPLIT:
|
|
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
|
|
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"}
|