@gesslar/actioneer 2.2.0 → 2.4.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/package.json +16 -17
- package/src/browser/lib/ActionBuilder.js +87 -65
- package/src/browser/lib/ActionHooks.js +35 -27
- package/src/browser/lib/ActionRunner.js +85 -40
- package/src/browser/lib/ActionWrapper.js +13 -6
- package/src/browser/lib/Activity.js +17 -13
- package/src/browser/lib/Piper.js +46 -22
- package/src/types/browser/lib/ActionBuilder.d.ts +80 -58
- package/src/types/browser/lib/ActionBuilder.d.ts.map +1 -1
- package/src/types/browser/lib/ActionHooks.d.ts +28 -26
- package/src/types/browser/lib/ActionHooks.d.ts.map +1 -1
- package/src/types/browser/lib/ActionRunner.d.ts +7 -5
- package/src/types/browser/lib/ActionRunner.d.ts.map +1 -1
- package/src/types/browser/lib/ActionWrapper.d.ts +14 -5
- package/src/types/browser/lib/ActionWrapper.d.ts.map +1 -1
- package/src/types/browser/lib/Activity.d.ts +20 -14
- package/src/types/browser/lib/Activity.d.ts.map +1 -1
- package/src/types/browser/lib/Piper.d.ts +14 -9
- package/src/types/browser/lib/Piper.d.ts.map +1 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {Promised, Data, Sass,
|
|
1
|
+
import {Promised, Data, Sass, Tantrum, Valid} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
3
|
import {ACTIVITY} from "./Activity.js"
|
|
4
4
|
import Piper from "./Piper.js"
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @typedef {import("./ActionBuilder.js").default} ActionBuilder
|
|
7
|
+
* Types
|
|
8
|
+
*
|
|
9
|
+
* @import {default as ActionBuilder} from "./ActionBuilder.js"
|
|
10
|
+
* @import {default as ActionWrapper} from "./ActionWrapper.js"
|
|
12
11
|
*/
|
|
13
|
-
|
|
14
12
|
/**
|
|
13
|
+
* @typedef {(message: string, level?: number, ...args: Array<unknown>) => void} DebugFn
|
|
14
|
+
*
|
|
15
15
|
* @typedef {object} ActionRunnerOptions
|
|
16
16
|
* @property {DebugFn} [debug] Logger function.
|
|
17
17
|
*/
|
|
@@ -24,9 +24,9 @@ import Piper from "./Piper.js"
|
|
|
24
24
|
* context object under `result.value` that can be replaced or enriched.
|
|
25
25
|
*/
|
|
26
26
|
export default class ActionRunner extends Piper {
|
|
27
|
-
/** @type {
|
|
27
|
+
/** @type {ActionBuilder?} */
|
|
28
28
|
#actionBuilder = null
|
|
29
|
-
/** @type {
|
|
29
|
+
/** @type {ActionWrapper?} */
|
|
30
30
|
#actionWrapper = null
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -36,13 +36,6 @@ 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
|
-
|
|
46
39
|
/**
|
|
47
40
|
* Instantiate a runner over an optional action wrapper.
|
|
48
41
|
*
|
|
@@ -62,9 +55,45 @@ export default class ActionRunner extends Piper {
|
|
|
62
55
|
|
|
63
56
|
this.#actionBuilder = actionBuilder
|
|
64
57
|
|
|
58
|
+
this.addSetup(this.#setupHooks)
|
|
65
59
|
this.addStep(this.run, {
|
|
66
60
|
name: `ActionRunner for ${actionBuilder.tag.description}`
|
|
67
61
|
})
|
|
62
|
+
this.addCleanup(this.#cleanupHooks)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Invokes the `setup` lifecycle hook on the raw hooks object, if defined.
|
|
67
|
+
* Registered as a Piper setup step so it fires before any items are processed.
|
|
68
|
+
*
|
|
69
|
+
* @param {unknown} ctx - Value passed by {@link Piper#pipe} (the items array).
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
async #setupHooks(ctx) {
|
|
74
|
+
const ab = this.#actionBuilder
|
|
75
|
+
const ah = ab?.hooks
|
|
76
|
+
const setup = ah?.setup
|
|
77
|
+
|
|
78
|
+
if(setup)
|
|
79
|
+
await setup.call(ah, ctx)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Invokes the `cleanup` lifecycle hook on the raw hooks object, if defined.
|
|
84
|
+
* Registered as a Piper teardown step so it fires after all items are processed.
|
|
85
|
+
*
|
|
86
|
+
* @param {unknown} ctx - Value passed by {@link Piper#pipe} (the items array).
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
* @private
|
|
89
|
+
*/
|
|
90
|
+
async #cleanupHooks(ctx) {
|
|
91
|
+
const ab = this.#actionBuilder
|
|
92
|
+
const ah = ab?.hooks
|
|
93
|
+
const cleanup = ah?.cleanup
|
|
94
|
+
|
|
95
|
+
if(cleanup)
|
|
96
|
+
await cleanup.call(ah, ctx)
|
|
68
97
|
}
|
|
69
98
|
|
|
70
99
|
/**
|
|
@@ -76,14 +105,17 @@ export default class ActionRunner extends Piper {
|
|
|
76
105
|
* @param {import("./ActionWrapper.js").default|null} [parentWrapper] - Parent wrapper for BREAK/CONTINUE signaling.
|
|
77
106
|
* @returns {Promise<unknown>} Final value produced by the pipeline.
|
|
78
107
|
* @throws {Sass} When no activities are registered, conflicting activity kinds are used, or execution fails.
|
|
108
|
+
* @throws {Tantrum} When both an activity and the done callback fail.
|
|
79
109
|
*/
|
|
80
110
|
async run(context, parentWrapper=null) {
|
|
81
111
|
if(!this.#actionWrapper)
|
|
82
|
-
this.#actionWrapper = await this.#actionBuilder.build()
|
|
112
|
+
this.#actionWrapper = await this.#actionBuilder.build(this)
|
|
83
113
|
|
|
84
114
|
const actionWrapper = this.#actionWrapper
|
|
85
115
|
const activities = Array.from(actionWrapper.activities)
|
|
86
116
|
|
|
117
|
+
let caughtError = null
|
|
118
|
+
|
|
87
119
|
try {
|
|
88
120
|
for(
|
|
89
121
|
let cursor = 0, max = activities.length;
|
|
@@ -114,7 +146,7 @@ export default class ActionRunner extends Piper {
|
|
|
114
146
|
|
|
115
147
|
if(await this.#evalPredicate(activity, context)) {
|
|
116
148
|
if(kindBreak) {
|
|
117
|
-
this
|
|
149
|
+
this.emit("loop.break", parentWrapper)
|
|
118
150
|
break
|
|
119
151
|
}
|
|
120
152
|
|
|
@@ -132,7 +164,7 @@ export default class ActionRunner extends Piper {
|
|
|
132
164
|
break
|
|
133
165
|
|
|
134
166
|
let weWereOnABreak = false
|
|
135
|
-
const breakReceiver = this
|
|
167
|
+
const breakReceiver = this.on("loop.break", wrapper => {
|
|
136
168
|
if(wrapper.id === actionWrapper.id) {
|
|
137
169
|
weWereOnABreak = true
|
|
138
170
|
}
|
|
@@ -158,10 +190,7 @@ export default class ActionRunner extends Piper {
|
|
|
158
190
|
)
|
|
159
191
|
|
|
160
192
|
const original = context
|
|
161
|
-
const splitContexts = await splitter.call(
|
|
162
|
-
activity.action,
|
|
163
|
-
context
|
|
164
|
-
)
|
|
193
|
+
const splitContexts = await splitter.call(activity.action,context)
|
|
165
194
|
|
|
166
195
|
let settled
|
|
167
196
|
|
|
@@ -199,20 +228,28 @@ export default class ActionRunner extends Piper {
|
|
|
199
228
|
throw Sass.new("ActionRunner running activity", error)
|
|
200
229
|
}
|
|
201
230
|
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
} catch(err) {
|
|
232
|
+
caughtError = err
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Execute done callback if registered - always runs, even on error
|
|
236
|
+
// Only run for top-level pipelines, not nested builders (inside loops)
|
|
237
|
+
if(actionWrapper.done && !parentWrapper) {
|
|
238
|
+
try {
|
|
239
|
+
context = await actionWrapper.done.call(
|
|
240
|
+
actionWrapper.action, caughtError ?? context
|
|
241
|
+
)
|
|
242
|
+
} catch(error) {
|
|
243
|
+
if(caughtError)
|
|
244
|
+
caughtError = new Tantrum("ActionRunner running done callback", [caughtError, error])
|
|
245
|
+
else
|
|
246
|
+
caughtError = Sass.new("ActionRunner running done callback", error)
|
|
213
247
|
}
|
|
214
248
|
}
|
|
215
249
|
|
|
250
|
+
if(caughtError)
|
|
251
|
+
throw caughtError
|
|
252
|
+
|
|
216
253
|
return context
|
|
217
254
|
}
|
|
218
255
|
|
|
@@ -248,10 +285,20 @@ export default class ActionRunner extends Piper {
|
|
|
248
285
|
debug: this.#debug, name: activity.name
|
|
249
286
|
})
|
|
250
287
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
288
|
+
// Forward loop.break events from nested runner to this runner
|
|
289
|
+
// so that parent WHILE/UNTIL loops can receive break signals.
|
|
290
|
+
const forwarder = runner.on("loop.break",
|
|
291
|
+
wrapper => this.emit("loop.break", wrapper)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
if(parallel) {
|
|
296
|
+
return await runner.pipe(context)
|
|
297
|
+
} else {
|
|
298
|
+
return await runner.run(context, activity.wrapper)
|
|
299
|
+
}
|
|
300
|
+
} finally {
|
|
301
|
+
forwarder()
|
|
255
302
|
}
|
|
256
303
|
} else if(opKind === "Function") {
|
|
257
304
|
try {
|
|
@@ -281,8 +328,6 @@ export default class ActionRunner extends Piper {
|
|
|
281
328
|
}
|
|
282
329
|
}
|
|
283
330
|
|
|
284
|
-
console.log(activity.opKind + " " + JSON.stringify(activity))
|
|
285
|
-
|
|
286
331
|
throw Sass.new("We buy Functions and ActionBuilders. Only. Not whatever that was.")
|
|
287
332
|
}
|
|
288
333
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import Activity from "./Activity.js"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Type imports
|
|
5
|
+
*
|
|
6
|
+
* @import {default as ActionHooks} from "./ActionHooks.js"
|
|
7
|
+
*/
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* @typedef {object} WrappedActivityConfig
|
|
5
11
|
* @property {string|symbol} name Activity identifier used by hooks/logs.
|
|
@@ -10,10 +16,6 @@ import Activity from "./Activity.js"
|
|
|
10
16
|
* @property {(message: string, level?: number, ...args: Array<unknown>) => void} [debug] Optional logger reference.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
|
-
/**
|
|
14
|
-
* @typedef {import("@gesslar/toolkit").Generator<Activity, void, unknown>} ActivityIterator
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
19
|
/**
|
|
18
20
|
* Thin wrapper that materialises {@link Activity} instances on demand.
|
|
19
21
|
*/
|
|
@@ -32,7 +34,7 @@ export default class ActionWrapper {
|
|
|
32
34
|
*/
|
|
33
35
|
#debug = () => {}
|
|
34
36
|
|
|
35
|
-
/** @type {
|
|
37
|
+
/** @type {ActionHooks} */
|
|
36
38
|
#hooks = null
|
|
37
39
|
/** @type {((context: unknown) => unknown|Promise<unknown>)|null} */
|
|
38
40
|
#done = null
|
|
@@ -44,7 +46,12 @@ export default class ActionWrapper {
|
|
|
44
46
|
/**
|
|
45
47
|
* Create a wrapper from the builder payload.
|
|
46
48
|
*
|
|
47
|
-
* @param {
|
|
49
|
+
* @param {object} init - Builder payload.
|
|
50
|
+
* @param {Map<string|symbol, WrappedActivityConfig>} init.activities - Registered activities.
|
|
51
|
+
* @param {(message: string, level?: number, ...args: Array<unknown>) => void} init.debug - Logger.
|
|
52
|
+
* @param {import("./ActionHooks.js").default?} init.hooks - Optional hooks instance.
|
|
53
|
+
* @param {((context: unknown) => unknown|Promise<unknown>)|null} [init.done] - Optional done callback.
|
|
54
|
+
* @param {unknown} [init.action] - Optional parent action instance.
|
|
48
55
|
*/
|
|
49
56
|
constructor({activities,hooks,debug,done: doneCallback,action}) {
|
|
50
57
|
this.#debug = debug
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {Data} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* @import {default as ActionBuilder} from "./ActionBuilder.js"
|
|
5
|
+
* @import {default as ActionHooks} from "./ActionHooks.js"
|
|
6
|
+
* @import {default as ActionWrapper} from "./ActionWrapper.js"
|
|
7
|
+
**/
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Activity bit flags recognised by the builder. The flag decides
|
|
@@ -29,13 +33,13 @@ export default class Activity {
|
|
|
29
33
|
#action = null
|
|
30
34
|
/** @type {unknown} */
|
|
31
35
|
#context = null
|
|
32
|
-
/** @type {ActionHooks
|
|
36
|
+
/** @type {ActionHooks?} */
|
|
33
37
|
#hooks = null
|
|
34
|
-
/** @type {number
|
|
38
|
+
/** @type {number?} */
|
|
35
39
|
#kind = null
|
|
36
40
|
/** @type {string|symbol} */
|
|
37
41
|
#name = null
|
|
38
|
-
/** @type {((context: unknown) => unknown|Promise<unknown>)|
|
|
42
|
+
/** @type {((context: unknown) => unknown|Promise<unknown>)|ActionBuilder} */
|
|
39
43
|
#op = null
|
|
40
44
|
/** @type {((context: unknown) => boolean|Promise<boolean>)|null} */
|
|
41
45
|
#pred = null
|
|
@@ -43,7 +47,7 @@ export default class Activity {
|
|
|
43
47
|
#rejoiner = null
|
|
44
48
|
/** @type {((context: unknown) => unknown)|null} */
|
|
45
49
|
#splitter = null
|
|
46
|
-
/** @type {
|
|
50
|
+
/** @type {ActionWrapper?} */
|
|
47
51
|
#wrapper = null
|
|
48
52
|
/** @type {symbol} */
|
|
49
53
|
#id = Symbol(performance.now())
|
|
@@ -54,13 +58,13 @@ export default class Activity {
|
|
|
54
58
|
* @param {object} init - Initial properties describing the activity operation, loop semantics, and predicate
|
|
55
59
|
* @param {unknown} init.action - Parent action instance
|
|
56
60
|
* @param {string|symbol} init.name - Activity identifier
|
|
57
|
-
* @param {(context: unknown) => unknown|Promise<unknown>|
|
|
61
|
+
* @param {(context: unknown) => unknown|Promise<unknown>|ActionBuilder} init.op - Operation to execute
|
|
58
62
|
* @param {number} [init.kind] - Optional loop semantics flags
|
|
59
63
|
* @param {(context: unknown) => boolean|Promise<boolean>} [init.pred] - Optional predicate for WHILE/UNTIL
|
|
60
64
|
* @param {ActionHooks} [init.hooks] - Optional hooks instance
|
|
61
65
|
* @param {(context: unknown) => unknown} [init.splitter] - Optional splitter function for SPLIT activities
|
|
62
66
|
* @param {(originalContext: unknown, splitResults: unknown) => unknown} [init.rejoiner] - Optional rejoiner function for SPLIT activities
|
|
63
|
-
* @param {
|
|
67
|
+
* @param {ActionWrapper} [init.wrapper] - Optional wrapper containing this activity
|
|
64
68
|
*/
|
|
65
69
|
constructor({action,name,op,kind,pred,hooks,splitter,rejoiner,wrapper}) {
|
|
66
70
|
this.#action = action
|
|
@@ -131,7 +135,7 @@ export default class Activity {
|
|
|
131
135
|
/**
|
|
132
136
|
* The operator to execute (function or nested ActionBuilder).
|
|
133
137
|
*
|
|
134
|
-
* @returns {(context: unknown) => unknown|Promise<unknown>|
|
|
138
|
+
* @returns {(context: unknown) => unknown|Promise<unknown>|ActionBuilder} - Activity operation
|
|
135
139
|
*/
|
|
136
140
|
get op() {
|
|
137
141
|
return this.#op
|
|
@@ -140,7 +144,7 @@ export default class Activity {
|
|
|
140
144
|
/**
|
|
141
145
|
* The splitter function for SPLIT activities.
|
|
142
146
|
*
|
|
143
|
-
* @returns {((context: unknown) => unknown)
|
|
147
|
+
* @returns {((context: unknown) => unknown)?} Splitter function or null
|
|
144
148
|
*/
|
|
145
149
|
get splitter() {
|
|
146
150
|
return this.#splitter
|
|
@@ -149,7 +153,7 @@ export default class Activity {
|
|
|
149
153
|
/**
|
|
150
154
|
* The rejoiner function for SPLIT activities.
|
|
151
155
|
*
|
|
152
|
-
* @returns {((originalContext: unknown, splitResults: unknown) => unknown)
|
|
156
|
+
* @returns {((originalContext: unknown, splitResults: unknown) => unknown)?} Rejoiner function or null
|
|
153
157
|
*/
|
|
154
158
|
get rejoiner() {
|
|
155
159
|
return this.#rejoiner
|
|
@@ -168,7 +172,7 @@ export default class Activity {
|
|
|
168
172
|
* Get the ActionWrapper containing this activity.
|
|
169
173
|
* Used by BREAK/CONTINUE to signal the parent loop.
|
|
170
174
|
*
|
|
171
|
-
* @returns {
|
|
175
|
+
* @returns {ActionWrapper?} The wrapper or null
|
|
172
176
|
*/
|
|
173
177
|
get wrapper() {
|
|
174
178
|
return this.#wrapper ?? null
|
|
@@ -188,7 +192,7 @@ export default class Activity {
|
|
|
188
192
|
const result = await this.#op.call(this.#action, context)
|
|
189
193
|
|
|
190
194
|
// after hook
|
|
191
|
-
await this.#hooks?.callHook("after", this.#name, context)
|
|
195
|
+
await this.#hooks?.callHook("after", this.#name, result, context)
|
|
192
196
|
|
|
193
197
|
return result
|
|
194
198
|
}
|
|
@@ -209,7 +213,7 @@ export default class Activity {
|
|
|
209
213
|
/**
|
|
210
214
|
* Get the hooks instance attached to this activity.
|
|
211
215
|
*
|
|
212
|
-
* @returns {ActionHooks
|
|
216
|
+
* @returns {ActionHooks?} The hooks instance or null
|
|
213
217
|
*/
|
|
214
218
|
get hooks() {
|
|
215
219
|
return this.#hooks
|
package/src/browser/lib/Piper.js
CHANGED
|
@@ -9,10 +9,16 @@
|
|
|
9
9
|
* - Error handling and reporting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {Promised, Sass
|
|
12
|
+
import {Data, Disposer, NotifyClass, Promised, Sass} from "@gesslar/toolkit"
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @import {Tantrum} from "@gesslar/toolkit"
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export default class Piper extends NotifyClass {
|
|
15
19
|
#debug
|
|
20
|
+
#disposer = Disposer
|
|
21
|
+
#abortedReason
|
|
16
22
|
|
|
17
23
|
#lifeCycle = new Map([
|
|
18
24
|
["setup", new Set()],
|
|
@@ -23,18 +29,32 @@ export default class Piper {
|
|
|
23
29
|
/**
|
|
24
30
|
* Create a Piper instance.
|
|
25
31
|
*
|
|
26
|
-
* @param {{debug?: (message: string, level?: number, ...args: Array<unknown>) => void}} [config] Optional configuration with debug function
|
|
32
|
+
* @param {{debug?: (message: string, level?: number, ...args: Array<unknown>) => void}} [config] - Optional configuration with debug function
|
|
27
33
|
*/
|
|
28
34
|
constructor({debug = (() => {})} = {}) {
|
|
35
|
+
super()
|
|
36
|
+
|
|
29
37
|
this.#debug = debug
|
|
38
|
+
|
|
39
|
+
this.#disposer.register(
|
|
40
|
+
this.on("abort", this.#abortCalled.bind(this))
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#abortCalled(reason) {
|
|
45
|
+
this.#abortedReason = reason
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get reason() {
|
|
49
|
+
return this.#abortedReason
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
/**
|
|
33
53
|
* Add a processing step to the pipeline
|
|
34
54
|
*
|
|
35
|
-
* @param {(context: unknown) => Promise<unknown>|unknown} fn Function that processes an item
|
|
36
|
-
* @param {{name?: string, required?: boolean}} [options] Step options
|
|
37
|
-
* @param {unknown} [newThis] Optional this binding
|
|
55
|
+
* @param {(context: unknown) => Promise<unknown>|unknown} fn - Function that processes an item
|
|
56
|
+
* @param {{name?: string, required?: boolean}} [options] - Step options
|
|
57
|
+
* @param {unknown} [newThis] - Optional this binding
|
|
38
58
|
* @returns {Piper} The pipeline instance (for chaining)
|
|
39
59
|
*/
|
|
40
60
|
addStep(fn, options = {}, newThis) {
|
|
@@ -51,7 +71,7 @@ export default class Piper {
|
|
|
51
71
|
/**
|
|
52
72
|
* Add setup hook that runs before processing starts.
|
|
53
73
|
*
|
|
54
|
-
* @param {() => Promise<void>|void} fn - Setup function executed before processing
|
|
74
|
+
* @param {(items: Array<unknown>) => Promise<void>|void} fn - Setup function executed before processing; receives the full items array.
|
|
55
75
|
* @param {unknown} [thisArg] - Optional this binding for the setup function
|
|
56
76
|
* @returns {Piper} - The pipeline instance
|
|
57
77
|
*/
|
|
@@ -64,7 +84,7 @@ export default class Piper {
|
|
|
64
84
|
/**
|
|
65
85
|
* Add cleanup hook that runs after processing completes
|
|
66
86
|
*
|
|
67
|
-
* @param {() => Promise<void>|void} fn - Cleanup function executed after processing
|
|
87
|
+
* @param {(items: Array<unknown>) => Promise<void>|void} fn - Cleanup function executed after processing; receives the full items array.
|
|
68
88
|
* @param {unknown} [thisArg] - Optional this binding for the cleanup function
|
|
69
89
|
* @returns {Piper} - The pipeline instance
|
|
70
90
|
*/
|
|
@@ -90,7 +110,7 @@ export default class Piper {
|
|
|
90
110
|
const allResults = new Array(items.length)
|
|
91
111
|
|
|
92
112
|
const processWorker = async() => {
|
|
93
|
-
while(true) {
|
|
113
|
+
while(true && !this.reason) {
|
|
94
114
|
const currentIndex = itemIndex++
|
|
95
115
|
|
|
96
116
|
if(currentIndex >= items.length)
|
|
@@ -101,7 +121,10 @@ export default class Piper {
|
|
|
101
121
|
try {
|
|
102
122
|
const result = await this.#processItem(item)
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
if(Data.isType(result, "Error"))
|
|
125
|
+
allResults[currentIndex] = {status: "rejected", reason: result}
|
|
126
|
+
else
|
|
127
|
+
allResults[currentIndex] = {status: "fulfilled", value: result}
|
|
105
128
|
} catch(error) {
|
|
106
129
|
allResults[currentIndex] = {status: "rejected", reason: error}
|
|
107
130
|
}
|
|
@@ -109,7 +132,7 @@ export default class Piper {
|
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
const setupResult = await Promised.settle(
|
|
112
|
-
[...this.#lifeCycle.get("setup")].map(e => Promise.resolve(e()))
|
|
135
|
+
[...this.#lifeCycle.get("setup")].map(e => Promise.resolve(e(items)))
|
|
113
136
|
)
|
|
114
137
|
|
|
115
138
|
this.#processResult("Setting up the pipeline.", setupResult)
|
|
@@ -127,36 +150,37 @@ export default class Piper {
|
|
|
127
150
|
} finally {
|
|
128
151
|
// Run cleanup hooks
|
|
129
152
|
const teardownResult = await Promised.settle(
|
|
130
|
-
[...this.#lifeCycle.get("teardown")].map(e => Promise.resolve(e()))
|
|
153
|
+
[...this.#lifeCycle.get("teardown")].map(e => Promise.resolve(e(items)))
|
|
131
154
|
)
|
|
132
155
|
|
|
133
156
|
this.#processResult("Tearing down the pipeline.", teardownResult)
|
|
134
157
|
}
|
|
135
158
|
|
|
159
|
+
if(this.reason)
|
|
160
|
+
this.emit("aborted", this.reason)
|
|
161
|
+
|
|
136
162
|
return allResults
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
/**
|
|
140
166
|
* Validate settleAll results and throw a combined error when rejected.
|
|
141
167
|
*
|
|
142
|
-
* @param {string} message Context message
|
|
143
|
-
* @param {Array<unknown>} settled Results from settleAll
|
|
144
168
|
* @private
|
|
169
|
+
* @param {string} message - Context message
|
|
170
|
+
* @param {Array<unknown>} settled - Results from settleAll
|
|
171
|
+
* @throws {Tantrum} - If any settled result was rejected
|
|
145
172
|
*/
|
|
146
|
-
#processResult(
|
|
147
|
-
if(
|
|
148
|
-
throw
|
|
149
|
-
message,
|
|
150
|
-
settled.filter(r => r.status==="rejected").map(r => r.reason)
|
|
151
|
-
)
|
|
173
|
+
#processResult(_message, settled) {
|
|
174
|
+
if(Promised.hasRejected(settled))
|
|
175
|
+
Promised.throw(settled)
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
/**
|
|
155
179
|
* Process a single item through all pipeline steps
|
|
156
180
|
*
|
|
157
|
-
* @param {unknown} item The item to process
|
|
158
|
-
* @returns {Promise<unknown>} Result from the final step
|
|
159
181
|
* @private
|
|
182
|
+
* @param {unknown} item - The item to process
|
|
183
|
+
* @returns {Promise<unknown>} Result from the final step
|
|
160
184
|
*/
|
|
161
185
|
async #processItem(item) {
|
|
162
186
|
// Execute each step in sequence
|