@gesslar/actioneer 0.2.3 → 0.2.6
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 +115 -5
- package/package.json +2 -2
- package/src/lib/ActionBuilder.js +40 -3
- package/src/lib/ActionHooks.js +4 -4
- package/src/lib/ActionRunner.js +41 -19
- package/src/lib/Piper.js +11 -7
- package/src/types/lib/ActionBuilder.d.ts +1 -1
package/README.md
CHANGED
|
@@ -30,8 +30,16 @@ class MyAction {
|
|
|
30
30
|
|
|
31
31
|
const builder = new ActionBuilder(new MyAction())
|
|
32
32
|
const runner = new ActionRunner(builder)
|
|
33
|
-
const
|
|
34
|
-
|
|
33
|
+
const results = await runner.pipe([{}], 4) // run up to 4 contexts concurrently
|
|
34
|
+
|
|
35
|
+
// pipe() returns settled results: {status: "fulfilled", value: ...} or {status: "rejected", reason: ...}
|
|
36
|
+
results.forEach(result => {
|
|
37
|
+
if (result.status === "fulfilled") {
|
|
38
|
+
console.log("Count:", result.value)
|
|
39
|
+
} else {
|
|
40
|
+
console.error("Error:", result.reason)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
35
43
|
```
|
|
36
44
|
|
|
37
45
|
## Types (TypeScript / VS Code)
|
|
@@ -154,9 +162,40 @@ class ParallelProcessor {
|
|
|
154
162
|
|
|
155
163
|
1. The **splitter** function receives the context and returns an array of contexts (one per parallel task)
|
|
156
164
|
2. Each split context is processed in parallel through the **operation** function
|
|
157
|
-
3. The **rejoiner** function receives the original context and the array of
|
|
165
|
+
3. The **rejoiner** function receives the original context and the array of settled results from `Promise.allSettled()`
|
|
158
166
|
4. The rejoiner combines the results and returns the updated context
|
|
159
167
|
|
|
168
|
+
**Important: SPLIT uses `Promise.allSettled()`**
|
|
169
|
+
|
|
170
|
+
The SPLIT mode uses `Promise.allSettled()` internally to execute parallel operations. This means your **rejoiner** function will receive an array of settlement objects, not the raw context values. Each element in the array will be either:
|
|
171
|
+
|
|
172
|
+
- `{ status: "fulfilled", value: <result> }` for successful operations
|
|
173
|
+
- `{ status: "rejected", reason: <error> }` for failed operations
|
|
174
|
+
|
|
175
|
+
Your rejoiner must handle settled results accordingly. You can process them however you need - check each `status` manually, or use helper utilities like those in `@gesslar/toolkit`:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import { Util } from "@gesslar/toolkit"
|
|
179
|
+
|
|
180
|
+
#rejoin = (originalCtx, settledResults) => {
|
|
181
|
+
// settledResults is an array of settlement objects
|
|
182
|
+
// Each has either { status: "fulfilled", value: ... }
|
|
183
|
+
// or { status: "rejected", reason: ... }
|
|
184
|
+
|
|
185
|
+
// Example: extract only successful results
|
|
186
|
+
originalCtx.results = Util.fulfilledValues(settledResults)
|
|
187
|
+
|
|
188
|
+
// Example: check for any failures
|
|
189
|
+
if (Util.anyRejected(settledResults)) {
|
|
190
|
+
originalCtx.errors = Util.rejectedReasons(
|
|
191
|
+
Util.settledAndRejected(settledResults)
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return originalCtx
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
160
199
|
**Nested Pipelines with SPLIT:**
|
|
161
200
|
|
|
162
201
|
You can use nested ActionBuilders with SPLIT mode for complex parallel workflows:
|
|
@@ -196,6 +235,68 @@ class NestedParallel {
|
|
|
196
235
|
| **UNTIL** | `.do(name, ACTIVITY.UNTIL, predicate, operation)` | After iteration | Loop until condition is true |
|
|
197
236
|
| **SPLIT** | `.do(name, ACTIVITY.SPLIT, splitter, rejoiner, operation)` | N/A | Parallel execution with split/rejoin |
|
|
198
237
|
|
|
238
|
+
## Running Actions: `run()` vs `pipe()`
|
|
239
|
+
|
|
240
|
+
ActionRunner provides two methods for executing your action pipelines:
|
|
241
|
+
|
|
242
|
+
### `run(context)` - Single Context Execution
|
|
243
|
+
|
|
244
|
+
Executes the pipeline once with a single context. Returns the final context value directly, or throws if an error occurs.
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
const builder = new ActionBuilder(new MyAction())
|
|
248
|
+
const runner = new ActionRunner(builder)
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const result = await runner.run({input: "data"})
|
|
252
|
+
console.log(result) // Final context value
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error("Pipeline failed:", error)
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Use `run()` when:**
|
|
259
|
+
|
|
260
|
+
- Processing a single context
|
|
261
|
+
- You want errors to throw immediately
|
|
262
|
+
- You prefer traditional try/catch error handling
|
|
263
|
+
|
|
264
|
+
### `pipe(contexts, maxConcurrent)` - Concurrent Batch Execution
|
|
265
|
+
|
|
266
|
+
Executes the pipeline concurrently across multiple contexts with a configurable concurrency limit. Returns an array of **settled results** - never throws on individual pipeline failures.
|
|
267
|
+
|
|
268
|
+
```js
|
|
269
|
+
const builder = new ActionBuilder(new MyAction())
|
|
270
|
+
const runner = new ActionRunner(builder)
|
|
271
|
+
|
|
272
|
+
const contexts = [{id: 1}, {id: 2}, {id: 3}]
|
|
273
|
+
const results = await runner.pipe(contexts, 4) // Max 4 concurrent
|
|
274
|
+
|
|
275
|
+
results.forEach((result, i) => {
|
|
276
|
+
if (result.status === "fulfilled") {
|
|
277
|
+
console.log(`Context ${i} succeeded:`, result.value)
|
|
278
|
+
} else {
|
|
279
|
+
console.error(`Context ${i} failed:`, result.reason)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Use `pipe()` when:**
|
|
285
|
+
|
|
286
|
+
- Processing multiple contexts in parallel
|
|
287
|
+
- You want to control concurrency (default: 10)
|
|
288
|
+
- You need all results (both successes and failures)
|
|
289
|
+
- Error handling should be at the call site
|
|
290
|
+
|
|
291
|
+
**Important: `pipe()` returns settled results**
|
|
292
|
+
|
|
293
|
+
The `pipe()` method uses `Promise.allSettled()` internally and returns an array of settlement objects:
|
|
294
|
+
|
|
295
|
+
- `{status: "fulfilled", value: <result>}` for successful executions
|
|
296
|
+
- `{status: "rejected", reason: <error>}` for failed executions
|
|
297
|
+
|
|
298
|
+
This design ensures error handling responsibility stays at the call site - you decide how to handle failures rather than the framework deciding for you.
|
|
299
|
+
|
|
199
300
|
## ActionHooks
|
|
200
301
|
|
|
201
302
|
Actioneer supports lifecycle hooks that can execute before and after each activity in your pipeline. Hooks are loaded from a module and can be configured either by file path or by providing a pre-instantiated hooks object.
|
|
@@ -335,13 +436,22 @@ Examples of minimal configs and one-liners to run them are in the project discus
|
|
|
335
436
|
|
|
336
437
|
## Testing
|
|
337
438
|
|
|
338
|
-
Run the
|
|
439
|
+
Run the comprehensive test suite with Node's built-in test runner:
|
|
339
440
|
|
|
340
441
|
```bash
|
|
341
442
|
npm test
|
|
342
443
|
```
|
|
343
444
|
|
|
344
|
-
The test suite
|
|
445
|
+
The test suite includes 150+ tests covering all core classes and behaviors:
|
|
446
|
+
|
|
447
|
+
- **Activity** - Activity definitions, ACTIVITY flags (WHILE, UNTIL, SPLIT), and execution
|
|
448
|
+
- **ActionBuilder** - Fluent builder API, activity registration, and hooks configuration
|
|
449
|
+
- **ActionWrapper** - Activity iteration and integration with ActionBuilder
|
|
450
|
+
- **ActionRunner** - Pipeline execution, loop semantics, nested builders, and error handling
|
|
451
|
+
- **ActionHooks** - Hook lifecycle, loading from files, and timeout handling
|
|
452
|
+
- **Piper** - Concurrent processing, worker pools, and lifecycle hooks
|
|
453
|
+
|
|
454
|
+
Tests are organized in `tests/unit/` with one file per class. All tests use Node's native test runner and assertion library.
|
|
345
455
|
|
|
346
456
|
## Publishing
|
|
347
457
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gesslar/actioneer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Ready? Set?? ACTION!! pew! pew! pew!",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"typescript": "^5.9.3"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@gesslar/toolkit": "^
|
|
59
|
+
"@gesslar/toolkit": "^1.9.1"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/src/lib/ActionBuilder.js
CHANGED
|
@@ -2,6 +2,7 @@ import {Data, Sass, Valid} from "@gesslar/toolkit"
|
|
|
2
2
|
|
|
3
3
|
import ActionWrapper from "./ActionWrapper.js"
|
|
4
4
|
import ActionHooks from "./ActionHooks.js"
|
|
5
|
+
import {ACTIVITY} from "./Activity.js"
|
|
5
6
|
|
|
6
7
|
/** @typedef {import("./ActionRunner.js").default} ActionRunner */
|
|
7
8
|
/** @typedef {typeof import("./Activity.js").ACTIVITY} ActivityFlags */
|
|
@@ -100,6 +101,7 @@ export default class ActionBuilder {
|
|
|
100
101
|
* Overloads:
|
|
101
102
|
* - do(name, op)
|
|
102
103
|
* - do(name, kind, pred, opOrWrapper)
|
|
104
|
+
* - do(name, kind, splitter, rejoiner, opOrWrapper)
|
|
103
105
|
*
|
|
104
106
|
* @overload
|
|
105
107
|
* @param {string|symbol} name Activity name
|
|
@@ -116,6 +118,16 @@ export default class ActionBuilder {
|
|
|
116
118
|
* @returns {ActionBuilder}
|
|
117
119
|
*/
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @overload
|
|
123
|
+
* @param {string|symbol} name Activity name
|
|
124
|
+
* @param {number} kind ACTIVITY.SPLIT flag.
|
|
125
|
+
* @param {(context: unknown) => unknown} splitter Splitter function for SPLIT mode.
|
|
126
|
+
* @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Rejoiner function for SPLIT mode.
|
|
127
|
+
* @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
|
|
128
|
+
* @returns {ActionBuilder}
|
|
129
|
+
*/
|
|
130
|
+
|
|
119
131
|
/**
|
|
120
132
|
* Handles runtime dispatch across the documented overloads.
|
|
121
133
|
*
|
|
@@ -128,7 +140,8 @@ export default class ActionBuilder {
|
|
|
128
140
|
|
|
129
141
|
// signatures
|
|
130
142
|
// name, [function] => once
|
|
131
|
-
// name, [number,function,function] => some kind of control operation
|
|
143
|
+
// name, [number,function,function] => some kind of control operation (WHILE/UNTIL)
|
|
144
|
+
// name, [number,function,function,function] => SPLIT operation with splitter/rejoiner
|
|
132
145
|
// name, [number,function,ActionBuilder] => some kind of branch
|
|
133
146
|
|
|
134
147
|
const action = this.#action
|
|
@@ -149,6 +162,19 @@ export default class ActionBuilder {
|
|
|
149
162
|
Valid.type(op, "Function|ActionBuilder")
|
|
150
163
|
|
|
151
164
|
Object.assign(activityDefinition, {kind, pred, op})
|
|
165
|
+
} else if(args.length === 4) {
|
|
166
|
+
const [kind, splitter, rejoiner, op] = args
|
|
167
|
+
|
|
168
|
+
Valid.type(kind, "Number")
|
|
169
|
+
Valid.type(splitter, "Function")
|
|
170
|
+
Valid.type(rejoiner, "Function")
|
|
171
|
+
Valid.type(op, "Function|ActionBuilder")
|
|
172
|
+
|
|
173
|
+
// Validate that kind is SPLIT
|
|
174
|
+
if((kind & ACTIVITY.SPLIT) !== ACTIVITY.SPLIT)
|
|
175
|
+
throw Sass.new("4-argument form of 'do' is only valid for ACTIVITY.SPLIT")
|
|
176
|
+
|
|
177
|
+
Object.assign(activityDefinition, {kind, splitter, rejoiner, op})
|
|
152
178
|
} else {
|
|
153
179
|
throw Sass.new("Invalid number of arguments passed to 'do'")
|
|
154
180
|
}
|
|
@@ -182,9 +208,14 @@ export default class ActionBuilder {
|
|
|
182
208
|
*
|
|
183
209
|
* @param {import("./ActionHooks.js").default} hooks An already-instantiated hooks instance.
|
|
184
210
|
* @returns {ActionBuilder} The builder instance for chaining.
|
|
185
|
-
* @throws {Sass} If hooks have already been configured.
|
|
211
|
+
* @throws {Sass} If hooks have already been configured with a different instance.
|
|
186
212
|
*/
|
|
187
213
|
withHooks(hooks) {
|
|
214
|
+
// If the same hooks instance is already set, this is idempotent - just return
|
|
215
|
+
if(this.#hooks === hooks) {
|
|
216
|
+
return this
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
|
|
189
220
|
Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
|
|
190
221
|
Valid.assert(this.#hooks === null, "Hooks have already been configured.")
|
|
@@ -236,8 +267,14 @@ export default class ActionBuilder {
|
|
|
236
267
|
const newHooks = ActionHooks.new
|
|
237
268
|
|
|
238
269
|
const hooks = this.#hooks
|
|
239
|
-
if(hooks)
|
|
270
|
+
if(hooks) {
|
|
271
|
+
// If hooks is already an ActionHooks instance, use it directly
|
|
272
|
+
if(hooks instanceof ActionHooks)
|
|
273
|
+
return hooks
|
|
274
|
+
|
|
275
|
+
// Otherwise, wrap it in a new ActionHooks instance
|
|
240
276
|
return await newHooks({hooks}, this.#debug)
|
|
277
|
+
}
|
|
241
278
|
|
|
242
279
|
const hooksFile = this.#hooksFile
|
|
243
280
|
const hooksKind = this.#hooksKind
|
package/src/lib/ActionHooks.js
CHANGED
|
@@ -114,7 +114,7 @@ export default class ActionHooks {
|
|
|
114
114
|
static async new(config, debug) {
|
|
115
115
|
debug("Creating new HookManager instance with args: %o", 2, config)
|
|
116
116
|
|
|
117
|
-
const instance = new ActionHooks(config, debug)
|
|
117
|
+
const instance = new ActionHooks({...config, debug})
|
|
118
118
|
if(!instance.#hooks) {
|
|
119
119
|
const hooksFile = new FileObject(instance.#hooksFile)
|
|
120
120
|
|
|
@@ -151,7 +151,7 @@ export default class ActionHooks {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
return
|
|
154
|
+
return instance
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
@@ -170,8 +170,8 @@ export default class ActionHooks {
|
|
|
170
170
|
if(!hooks)
|
|
171
171
|
return
|
|
172
172
|
|
|
173
|
-
const stringActivityName = Data.isType("Symbol")
|
|
174
|
-
? activityName.description
|
|
173
|
+
const stringActivityName = Data.isType(activityName, "Symbol")
|
|
174
|
+
? activityName.description
|
|
175
175
|
: activityName
|
|
176
176
|
|
|
177
177
|
const hookName = this.#getActivityHookName(kind, stringActivityName)
|
package/src/lib/ActionRunner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Data, Sass, Valid} from "@gesslar/toolkit"
|
|
1
|
+
import {Data, Sass, Util, Valid} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
3
|
import ActionBuilder from "./ActionBuilder.js"
|
|
4
4
|
import {ACTIVITY} from "./Activity.js"
|
|
@@ -112,20 +112,46 @@ export default class ActionRunner extends Piper {
|
|
|
112
112
|
if(await this.#hasPredicate(activity,predicate,context))
|
|
113
113
|
break
|
|
114
114
|
}
|
|
115
|
-
} else if(kindSplit
|
|
116
|
-
// SPLIT activity: parallel execution with splitter/rejoiner
|
|
117
|
-
|
|
118
|
-
const rejoiner = activity
|
|
115
|
+
} else if(kindSplit) {
|
|
116
|
+
// SPLIT activity: parallel execution with splitter/rejoiner
|
|
117
|
+
// pattern
|
|
118
|
+
const {splitter, rejoiner} = activity
|
|
119
119
|
|
|
120
120
|
if(!splitter || !rejoiner)
|
|
121
121
|
throw Sass.new(
|
|
122
|
-
`SPLIT activity "${String(activity.name)}" requires both
|
|
122
|
+
`SPLIT activity "${String(activity.name)}" requires both ` +
|
|
123
|
+
`splitter and rejoiner functions.`
|
|
123
124
|
)
|
|
124
125
|
|
|
125
126
|
const original = context
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
const splitContexts = await splitter.call(activity.action, context)
|
|
128
|
+
|
|
129
|
+
let settled
|
|
130
|
+
|
|
131
|
+
if(activity.opKind === "ActionBuilder") {
|
|
132
|
+
// Use parallel execution for ActionBuilder with concurrency control
|
|
133
|
+
// pipe() now returns settled results
|
|
134
|
+
if(activity.hooks)
|
|
135
|
+
activity.op.withHooks(activity.hooks)
|
|
136
|
+
|
|
137
|
+
const runner = new this.constructor(activity.op, {
|
|
138
|
+
debug: this.#debug, name: activity.name
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// pipe() returns settled results with concurrency control
|
|
142
|
+
settled = await runner.pipe(splitContexts)
|
|
143
|
+
} else {
|
|
144
|
+
// For plain functions, process each split context
|
|
145
|
+
settled = await Util.settleAll(
|
|
146
|
+
splitContexts.map(ctx => this.#execute(activity, ctx))
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const rejoined = await rejoiner.call(
|
|
151
|
+
activity.action,
|
|
152
|
+
original,
|
|
153
|
+
settled
|
|
154
|
+
)
|
|
129
155
|
|
|
130
156
|
context = rejoined
|
|
131
157
|
} else {
|
|
@@ -147,7 +173,7 @@ export default class ActionRunner extends Piper {
|
|
|
147
173
|
*
|
|
148
174
|
* When parallel=true, uses Piper.pipe() for concurrent execution with worker pool pattern.
|
|
149
175
|
* This is triggered by SPLIT activities where context is divided for parallel processing.
|
|
150
|
-
* Results from parallel execution are
|
|
176
|
+
* Results from parallel execution are returned directly as an array from Piper.pipe().
|
|
151
177
|
*
|
|
152
178
|
* @param {import("./Activity.js").default} activity Pipeline activity descriptor.
|
|
153
179
|
* @param {unknown} context Current pipeline context.
|
|
@@ -162,17 +188,15 @@ export default class ActionRunner extends Piper {
|
|
|
162
188
|
const opKind = activity.opKind
|
|
163
189
|
|
|
164
190
|
if(opKind === "ActionBuilder") {
|
|
165
|
-
if(activity.hooks
|
|
166
|
-
activity.op.
|
|
191
|
+
if(activity.hooks)
|
|
192
|
+
activity.op.withHooks(activity.hooks)
|
|
167
193
|
|
|
168
194
|
const runner = new this.constructor(activity.op, {
|
|
169
195
|
debug: this.#debug, name: activity.name
|
|
170
196
|
})
|
|
171
197
|
|
|
172
198
|
if(parallel) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return piped.filter(p => p.ok).map(p => p.value)
|
|
199
|
+
return await runner.pipe(context)
|
|
176
200
|
} else {
|
|
177
201
|
return await runner.run(context)
|
|
178
202
|
}
|
|
@@ -182,16 +206,14 @@ export default class ActionRunner extends Piper {
|
|
|
182
206
|
|
|
183
207
|
if(Data.isType(result, "ActionBuilder")) {
|
|
184
208
|
if(activity.hooks)
|
|
185
|
-
result.
|
|
209
|
+
result.withHooks(activity.hooks)
|
|
186
210
|
|
|
187
211
|
const runner = new this.constructor(result, {
|
|
188
212
|
debug: this.#debug, name: result.name
|
|
189
213
|
})
|
|
190
214
|
|
|
191
215
|
if(parallel) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return piped.filter(p => p.ok).map(p => p.value)
|
|
216
|
+
return await runner.pipe(context)
|
|
195
217
|
} else {
|
|
196
218
|
return await runner.run(context)
|
|
197
219
|
}
|
package/src/lib/Piper.js
CHANGED
|
@@ -79,7 +79,7 @@ export default class Piper {
|
|
|
79
79
|
*
|
|
80
80
|
* @param {Array<unknown>|unknown} items - Items to process
|
|
81
81
|
* @param {number} maxConcurrent - Maximum concurrent items to process
|
|
82
|
-
* @returns {Promise<Array<unknown>>} -
|
|
82
|
+
* @returns {Promise<Array<{status: string, value?: unknown, reason?: unknown}>>} - Settled results from processing
|
|
83
83
|
*/
|
|
84
84
|
async pipe(items, maxConcurrent = 10) {
|
|
85
85
|
items = Array.isArray(items)
|
|
@@ -87,20 +87,23 @@ export default class Piper {
|
|
|
87
87
|
: [items]
|
|
88
88
|
|
|
89
89
|
let itemIndex = 0
|
|
90
|
-
const allResults =
|
|
90
|
+
const allResults = new Array(items.length)
|
|
91
91
|
|
|
92
92
|
const processWorker = async() => {
|
|
93
93
|
while(true) {
|
|
94
94
|
const currentIndex = itemIndex++
|
|
95
|
+
|
|
95
96
|
if(currentIndex >= items.length)
|
|
96
97
|
break
|
|
97
98
|
|
|
98
99
|
const item = items[currentIndex]
|
|
100
|
+
|
|
99
101
|
try {
|
|
100
102
|
const result = await this.#processItem(item)
|
|
101
|
-
|
|
103
|
+
|
|
104
|
+
allResults[currentIndex] = {status: "fulfilled", value: result}
|
|
102
105
|
} catch(error) {
|
|
103
|
-
|
|
106
|
+
allResults[currentIndex] = {status: "rejected", reason: error}
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
}
|
|
@@ -108,6 +111,7 @@ export default class Piper {
|
|
|
108
111
|
const setupResult = await Util.settleAll(
|
|
109
112
|
[...this.#lifeCycle.get("setup")].map(e => e())
|
|
110
113
|
)
|
|
114
|
+
|
|
111
115
|
this.#processResult("Setting up the pipeline.", setupResult)
|
|
112
116
|
|
|
113
117
|
try {
|
|
@@ -118,14 +122,14 @@ export default class Piper {
|
|
|
118
122
|
for(let i = 0; i < workerCount; i++)
|
|
119
123
|
workers.push(processWorker())
|
|
120
124
|
|
|
121
|
-
// Wait for all workers to complete
|
|
122
|
-
|
|
123
|
-
this.#processResult("Processing pipeline.", processResult)
|
|
125
|
+
// Wait for all workers to complete - don't throw on worker failures
|
|
126
|
+
await Promise.all(workers)
|
|
124
127
|
} finally {
|
|
125
128
|
// Run cleanup hooks
|
|
126
129
|
const teardownResult = await Util.settleAll(
|
|
127
130
|
[...this.#lifeCycle.get("teardown")].map(e => e())
|
|
128
131
|
)
|
|
132
|
+
|
|
129
133
|
this.#processResult("Tearing down the pipeline.", teardownResult)
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -89,7 +89,7 @@ export default class ActionBuilder {
|
|
|
89
89
|
*
|
|
90
90
|
* @param {import("./ActionHooks.js").default} hooks An already-instantiated hooks instance.
|
|
91
91
|
* @returns {ActionBuilder} The builder instance for chaining.
|
|
92
|
-
* @throws {Sass} If hooks have already been configured.
|
|
92
|
+
* @throws {Sass} If hooks have already been configured with a different instance.
|
|
93
93
|
*/
|
|
94
94
|
withHooks(hooks: import('./ActionHooks.js').default): ActionBuilder
|
|
95
95
|
/**
|