@gesslar/actioneer 0.2.1 → 0.2.3
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 +152 -0
- package/package.json +1 -1
- package/src/lib/ActionBuilder.js +26 -106
- package/src/lib/ActionHooks.js +30 -46
- package/src/lib/ActionRunner.js +1 -1
- package/src/lib/ActionWrapper.js +7 -17
- package/src/lib/Activity.js +1 -1
- package/src/lib/Piper.js +52 -99
- package/src/types/lib/ActionBuilder.d.ts +2 -42
- package/src/types/lib/ActionBuilder.d.ts.map +1 -1
- package/src/types/lib/ActionHooks.d.ts +24 -23
- package/src/types/lib/ActionHooks.d.ts.map +1 -1
- package/src/types/lib/ActionRunner.d.ts +2 -4
- package/src/types/lib/ActionRunner.d.ts.map +1 -1
- package/src/types/lib/ActionWrapper.d.ts +5 -19
- package/src/types/lib/ActionWrapper.d.ts.map +1 -1
- package/src/types/lib/Activity.d.ts +19 -54
- package/src/types/lib/Activity.d.ts.map +1 -1
- package/src/types/lib/Piper.d.ts +7 -24
- package/src/types/lib/Piper.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,158 @@ import { ActionBuilder, ActionRunner } from "@gesslar/actioneer"
|
|
|
44
44
|
|
|
45
45
|
If you'd like more complete typings or additional JSDoc, open an issue or send a PR — contributions welcome.
|
|
46
46
|
|
|
47
|
+
## Activity Modes
|
|
48
|
+
|
|
49
|
+
Actioneer supports four distinct execution modes for activities, allowing you to control how operations are executed:
|
|
50
|
+
|
|
51
|
+
### Execute Once (Default)
|
|
52
|
+
|
|
53
|
+
The simplest mode executes an activity exactly once per context:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
class MyAction {
|
|
57
|
+
setup(builder) {
|
|
58
|
+
builder.do("processItem", ctx => {
|
|
59
|
+
ctx.result = ctx.input * 2
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### WHILE Mode
|
|
66
|
+
|
|
67
|
+
Loops while a predicate returns `true`. The predicate is evaluated **before** each iteration:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
71
|
+
|
|
72
|
+
class CounterAction {
|
|
73
|
+
#shouldContinue = (ctx) => ctx.count < 10
|
|
74
|
+
|
|
75
|
+
#increment = (ctx) => {
|
|
76
|
+
ctx.count += 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setup(builder) {
|
|
80
|
+
builder
|
|
81
|
+
.do("initialize", ctx => { ctx.count = 0 })
|
|
82
|
+
.do("countUp", ACTIVITY.WHILE, this.#shouldContinue, this.#increment)
|
|
83
|
+
.do("finish", ctx => { return ctx.count })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The activity will continue executing as long as the predicate returns `true`. Once it returns `false`, execution moves to the next activity.
|
|
89
|
+
|
|
90
|
+
### UNTIL Mode
|
|
91
|
+
|
|
92
|
+
Loops until a predicate returns `true`. The predicate is evaluated **after** each iteration:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
96
|
+
|
|
97
|
+
class ProcessorAction {
|
|
98
|
+
#queueIsEmpty = (ctx) => ctx.queue.length === 0
|
|
99
|
+
|
|
100
|
+
#processItem = (ctx) => {
|
|
101
|
+
const item = ctx.queue.shift()
|
|
102
|
+
ctx.processed.push(item)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setup(builder) {
|
|
106
|
+
builder
|
|
107
|
+
.do("initialize", ctx => {
|
|
108
|
+
ctx.queue = [1, 2, 3, 4, 5]
|
|
109
|
+
ctx.processed = []
|
|
110
|
+
})
|
|
111
|
+
.do("process", ACTIVITY.UNTIL, this.#queueIsEmpty, this.#processItem)
|
|
112
|
+
.do("finish", ctx => { return ctx.processed })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The activity executes at least once, then continues while the predicate returns `false`. Once it returns `true`, execution moves to the next activity.
|
|
118
|
+
|
|
119
|
+
### SPLIT Mode
|
|
120
|
+
|
|
121
|
+
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:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
125
|
+
|
|
126
|
+
class ParallelProcessor {
|
|
127
|
+
#split = (ctx) => {
|
|
128
|
+
// Split context into multiple items for parallel processing
|
|
129
|
+
return ctx.items.map(item => ({ item, processedBy: "worker" }))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#rejoin = (originalCtx, splitResults) => {
|
|
133
|
+
// Recombine parallel results back into original context
|
|
134
|
+
originalCtx.results = splitResults.map(r => r.item)
|
|
135
|
+
return originalCtx
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#processItem = (ctx) => {
|
|
139
|
+
ctx.item = ctx.item.toUpperCase()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setup(builder) {
|
|
143
|
+
builder
|
|
144
|
+
.do("initialize", ctx => {
|
|
145
|
+
ctx.items = ["apple", "banana", "cherry"]
|
|
146
|
+
})
|
|
147
|
+
.do("parallel", ACTIVITY.SPLIT, this.#split, this.#rejoin, this.#processItem)
|
|
148
|
+
.do("finish", ctx => { return ctx.results })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**How SPLIT Mode Works:**
|
|
154
|
+
|
|
155
|
+
1. The **splitter** function receives the context and returns an array of contexts (one per parallel task)
|
|
156
|
+
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 processed results
|
|
158
|
+
4. The rejoiner combines the results and returns the updated context
|
|
159
|
+
|
|
160
|
+
**Nested Pipelines with SPLIT:**
|
|
161
|
+
|
|
162
|
+
You can use nested ActionBuilders with SPLIT mode for complex parallel workflows:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
class NestedParallel {
|
|
166
|
+
#split = (ctx) => ctx.batches.map(batch => ({ batch }))
|
|
167
|
+
|
|
168
|
+
#rejoin = (original, results) => {
|
|
169
|
+
original.processed = results.flatMap(r => r.batch)
|
|
170
|
+
return original
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setup(builder) {
|
|
174
|
+
builder
|
|
175
|
+
.do("parallel", ACTIVITY.SPLIT, this.#split, this.#rejoin,
|
|
176
|
+
new ActionBuilder(this)
|
|
177
|
+
.do("step1", ctx => { /* ... */ })
|
|
178
|
+
.do("step2", ctx => { /* ... */ })
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Mode Constraints
|
|
185
|
+
|
|
186
|
+
- **Only one mode per activity**: You cannot combine WHILE, UNTIL, and SPLIT. Attempting to use multiple modes will throw an error: `"You can't combine activity kinds. Pick one: WHILE, UNTIL, or SPLIT!"`
|
|
187
|
+
- **SPLIT requires both functions**: The splitter and rejoiner are both mandatory for SPLIT mode
|
|
188
|
+
- **Predicates must return boolean**: For WHILE and UNTIL modes, predicates should return `true` or `false`
|
|
189
|
+
|
|
190
|
+
### Mode Summary Table
|
|
191
|
+
|
|
192
|
+
| Mode | Signature | Predicate Timing | Use Case |
|
|
193
|
+
| ----------- | ---------------------------------------------------------- | ---------------- | ------------------------------------ |
|
|
194
|
+
| **Default** | `.do(name, operation)` | N/A | Execute once per context |
|
|
195
|
+
| **WHILE** | `.do(name, ACTIVITY.WHILE, predicate, operation)` | Before iteration | Loop while condition is true |
|
|
196
|
+
| **UNTIL** | `.do(name, ACTIVITY.UNTIL, predicate, operation)` | After iteration | Loop until condition is true |
|
|
197
|
+
| **SPLIT** | `.do(name, ACTIVITY.SPLIT, splitter, rejoiner, operation)` | N/A | Parallel execution with split/rejoin |
|
|
198
|
+
|
|
47
199
|
## ActionHooks
|
|
48
200
|
|
|
49
201
|
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.
|
package/package.json
CHANGED
package/src/lib/ActionBuilder.js
CHANGED
|
@@ -30,8 +30,6 @@ import ActionHooks from "./ActionHooks.js"
|
|
|
30
30
|
* @property {ActionFunction|import("./ActionWrapper.js").default} op Operation to execute.
|
|
31
31
|
* @property {number} [kind] Optional kind flags from {@link ActivityFlags}.
|
|
32
32
|
* @property {(context: unknown) => boolean|Promise<boolean>} [pred] Loop predicate.
|
|
33
|
-
* @property {(context: unknown) => unknown} [splitter] Function to split context for parallel execution (SPLIT activities).
|
|
34
|
-
* @property {(originalContext: unknown, splitResults: unknown) => unknown} [rejoiner] Function to rejoin split results (SPLIT activities).
|
|
35
33
|
*/
|
|
36
34
|
|
|
37
35
|
/**
|
|
@@ -67,29 +65,15 @@ export default class ActionBuilder {
|
|
|
67
65
|
#debug = null
|
|
68
66
|
/** @type {symbol|null} */
|
|
69
67
|
#tag = null
|
|
70
|
-
/** @type {string|null} */
|
|
71
68
|
#hooksFile = null
|
|
72
|
-
/** @type {string|null} */
|
|
73
69
|
#hooksKind = null
|
|
74
|
-
/** @type {unknown|null} */
|
|
75
70
|
#hooks = null
|
|
76
|
-
/** @type {import("./ActionHooks.js").default|null} */
|
|
77
|
-
#actionHooks = null
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get the builder's tag symbol.
|
|
81
|
-
*
|
|
82
|
-
* @returns {symbol|null} The tag symbol for this builder instance
|
|
83
|
-
*/
|
|
84
|
-
get tag() {
|
|
85
|
-
return this.#tag
|
|
86
|
-
}
|
|
87
71
|
|
|
88
72
|
/**
|
|
89
73
|
* Creates a new ActionBuilder instance with the provided action callback.
|
|
90
74
|
*
|
|
91
|
-
* @param {ActionBuilderAction} [action]
|
|
92
|
-
* @param {ActionBuilderConfig} [config]
|
|
75
|
+
* @param {ActionBuilderAction} [action] Base action invoked by the runner when a block satisfies the configured structure.
|
|
76
|
+
* @param {ActionBuilderConfig} [config] Options
|
|
93
77
|
*/
|
|
94
78
|
constructor(
|
|
95
79
|
action,
|
|
@@ -106,6 +90,10 @@ export default class ActionBuilder {
|
|
|
106
90
|
}
|
|
107
91
|
}
|
|
108
92
|
|
|
93
|
+
get tag() {
|
|
94
|
+
return this.#tag
|
|
95
|
+
}
|
|
96
|
+
|
|
109
97
|
/**
|
|
110
98
|
* Register an activity that the runner can execute.
|
|
111
99
|
*
|
|
@@ -128,16 +116,6 @@ export default class ActionBuilder {
|
|
|
128
116
|
* @returns {ActionBuilder}
|
|
129
117
|
*/
|
|
130
118
|
|
|
131
|
-
/**
|
|
132
|
-
* @overload
|
|
133
|
-
* @param {string|symbol} name Activity name
|
|
134
|
-
* @param {number} kind Kind bitfield (ACTIVITY.SPLIT).
|
|
135
|
-
* @param {(context: unknown) => unknown} splitter Function to split context for parallel execution.
|
|
136
|
-
* @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Function to rejoin split results with original context.
|
|
137
|
-
* @param {ActionFunction|ActionBuilder} op Operation or nested ActionBuilder to execute on split context.
|
|
138
|
-
* @returns {ActionBuilder}
|
|
139
|
-
*/
|
|
140
|
-
|
|
141
119
|
/**
|
|
142
120
|
* Handles runtime dispatch across the documented overloads.
|
|
143
121
|
*
|
|
@@ -155,32 +133,22 @@ export default class ActionBuilder {
|
|
|
155
133
|
|
|
156
134
|
const action = this.#action
|
|
157
135
|
const debug = this.#debug
|
|
158
|
-
const activityDefinition = {name,action,debug}
|
|
136
|
+
const activityDefinition = {name, action, debug}
|
|
159
137
|
|
|
160
138
|
if(args.length === 1) {
|
|
161
|
-
const [op,kind] = args
|
|
139
|
+
const [op, kind] = args
|
|
162
140
|
Valid.type(kind, "Number|undefined")
|
|
163
141
|
Valid.type(op, "Function")
|
|
164
142
|
|
|
165
|
-
Object.assign(activityDefinition, {op,kind})
|
|
143
|
+
Object.assign(activityDefinition, {op, kind})
|
|
166
144
|
} else if(args.length === 3) {
|
|
167
|
-
const [kind,pred,op] = args
|
|
145
|
+
const [kind, pred, op] = args
|
|
168
146
|
|
|
169
147
|
Valid.type(kind, "Number")
|
|
170
148
|
Valid.type(pred, "Function")
|
|
171
149
|
Valid.type(op, "Function|ActionBuilder")
|
|
172
150
|
|
|
173
|
-
Object.assign(activityDefinition, {kind,pred,op})
|
|
174
|
-
} else if(args.length === 4) {
|
|
175
|
-
const [kind,splitter,rejoiner,op] = args
|
|
176
|
-
|
|
177
|
-
Valid.type(kind, "Number")
|
|
178
|
-
Valid.type(splitter, "Function")
|
|
179
|
-
Valid.type(rejoiner, "Function")
|
|
180
|
-
Valid.type(op, "Function|ActionBuilder")
|
|
181
|
-
|
|
182
|
-
Object.assign(activityDefinition, {kind,splitter,rejoiner,op})
|
|
183
|
-
|
|
151
|
+
Object.assign(activityDefinition, {kind, pred, op})
|
|
184
152
|
} else {
|
|
185
153
|
throw Sass.new("Invalid number of arguments passed to 'do'")
|
|
186
154
|
}
|
|
@@ -199,7 +167,9 @@ export default class ActionBuilder {
|
|
|
199
167
|
* @throws {Sass} If hooks have already been configured.
|
|
200
168
|
*/
|
|
201
169
|
withHooksFile(hooksFile, hooksKind) {
|
|
202
|
-
Valid.assert(this.#
|
|
170
|
+
Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
|
|
171
|
+
Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
|
|
172
|
+
Valid.assert(this.#hooks === null, "Hooks have already been configured.")
|
|
203
173
|
|
|
204
174
|
this.#hooksFile = hooksFile
|
|
205
175
|
this.#hooksKind = hooksKind
|
|
@@ -215,40 +185,15 @@ export default class ActionBuilder {
|
|
|
215
185
|
* @throws {Sass} If hooks have already been configured.
|
|
216
186
|
*/
|
|
217
187
|
withHooks(hooks) {
|
|
218
|
-
Valid.assert(this.#
|
|
188
|
+
Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
|
|
189
|
+
Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
|
|
190
|
+
Valid.assert(this.#hooks === null, "Hooks have already been configured.")
|
|
219
191
|
|
|
220
192
|
this.#hooks = hooks
|
|
221
193
|
|
|
222
194
|
return this
|
|
223
195
|
}
|
|
224
196
|
|
|
225
|
-
/**
|
|
226
|
-
* Configure hooks using an ActionHooks instance directly (typically used internally).
|
|
227
|
-
*
|
|
228
|
-
* @param {import("./ActionHooks.js").default} actionHooks Pre-configured ActionHooks instance.
|
|
229
|
-
* @returns {ActionBuilder} The builder instance for chaining.
|
|
230
|
-
* @throws {Sass} If hooks have already been configured.
|
|
231
|
-
*/
|
|
232
|
-
withActionHooks(actionHooks) {
|
|
233
|
-
Valid.assert(this.#exclusiveHooksCheck(), "Hooks have already been configured.")
|
|
234
|
-
|
|
235
|
-
this.#actionHooks = actionHooks
|
|
236
|
-
|
|
237
|
-
return this
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Ensures only one hooks configuration method is used at a time.
|
|
242
|
-
*
|
|
243
|
-
* @returns {boolean} True if no hooks have been configured yet, false otherwise.
|
|
244
|
-
* @private
|
|
245
|
-
*/
|
|
246
|
-
#exclusiveHooksCheck() {
|
|
247
|
-
return !!(this.#hooksFile && this.#hooksKind) +
|
|
248
|
-
!!(this.#hooks) +
|
|
249
|
-
!!(this.#actionHooks) === 0
|
|
250
|
-
}
|
|
251
|
-
|
|
252
197
|
/**
|
|
253
198
|
* Validates that an activity name has not been reused.
|
|
254
199
|
*
|
|
@@ -270,8 +215,6 @@ export default class ActionBuilder {
|
|
|
270
215
|
*/
|
|
271
216
|
async build() {
|
|
272
217
|
const action = this.#action
|
|
273
|
-
const activities = this.#activities
|
|
274
|
-
const debug = this.#debug
|
|
275
218
|
|
|
276
219
|
if(!action.tag) {
|
|
277
220
|
action.tag = this.#tag
|
|
@@ -282,47 +225,24 @@ export default class ActionBuilder {
|
|
|
282
225
|
// All children in a branch also get the same hooks.
|
|
283
226
|
const hooks = await this.#getHooks()
|
|
284
227
|
|
|
285
|
-
return new ActionWrapper({
|
|
228
|
+
return new ActionWrapper({
|
|
229
|
+
activities: this.#activities,
|
|
230
|
+
debug: this.#debug,
|
|
231
|
+
hooks,
|
|
232
|
+
})
|
|
286
233
|
}
|
|
287
234
|
|
|
288
|
-
/**
|
|
289
|
-
* Check if this builder has ActionHooks configured.
|
|
290
|
-
*
|
|
291
|
-
* @returns {boolean} True if ActionHooks have been configured.
|
|
292
|
-
*/
|
|
293
|
-
get hasActionHooks() {
|
|
294
|
-
return this.#actionHooks !== null
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Internal method to retrieve or create ActionHooks instance.
|
|
299
|
-
* Caches the hooks instance to avoid redundant instantiation.
|
|
300
|
-
*
|
|
301
|
-
* @returns {Promise<import("./ActionHooks.js").default|undefined>} The ActionHooks instance if configured.
|
|
302
|
-
* @private
|
|
303
|
-
*/
|
|
304
235
|
async #getHooks() {
|
|
305
|
-
if(this.#actionHooks) {
|
|
306
|
-
return this.#actionHooks
|
|
307
|
-
}
|
|
308
|
-
|
|
309
236
|
const newHooks = ActionHooks.new
|
|
310
237
|
|
|
311
238
|
const hooks = this.#hooks
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
this.#actionHooks = await newHooks({hooks}, this.#debug)
|
|
315
|
-
|
|
316
|
-
return this.#actionHooks
|
|
317
|
-
}
|
|
239
|
+
if(hooks)
|
|
240
|
+
return await newHooks({hooks}, this.#debug)
|
|
318
241
|
|
|
319
242
|
const hooksFile = this.#hooksFile
|
|
320
243
|
const hooksKind = this.#hooksKind
|
|
321
244
|
|
|
322
|
-
if(hooksFile && hooksKind)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return this.#actionHooks
|
|
326
|
-
}
|
|
245
|
+
if(hooksFile && hooksKind)
|
|
246
|
+
return await newHooks({hooksFile,hooksKind}, this.#debug)
|
|
327
247
|
}
|
|
328
248
|
}
|
package/src/lib/ActionHooks.js
CHANGED
|
@@ -7,10 +7,11 @@ import {Data, FileObject, Sass, Util, Valid} from "@gesslar/toolkit"
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {object} ActionHooksConfig
|
|
10
|
-
* @property {string}
|
|
11
|
-
* @property {FileObject
|
|
12
|
-
* @property {unknown} [
|
|
10
|
+
* @property {string} actionKind Action identifier shared between runner and hooks.
|
|
11
|
+
* @property {FileObject} hooksFile File handle used to import the hooks module.
|
|
12
|
+
* @property {unknown} [hooks] Already-instantiated hooks implementation (skips loading).
|
|
13
13
|
* @property {number} [hookTimeout] Timeout applied to hook execution in milliseconds.
|
|
14
|
+
* @property {DebugFn} debug Logger to emit diagnostics.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -26,7 +27,7 @@ export default class ActionHooks {
|
|
|
26
27
|
/** @type {FileObject|null} */
|
|
27
28
|
#hooksFile = null
|
|
28
29
|
/** @type {HookModule|null} */
|
|
29
|
-
#
|
|
30
|
+
#hooks = null
|
|
30
31
|
/** @type {string|null} */
|
|
31
32
|
#actionKind = null
|
|
32
33
|
/** @type {number} */
|
|
@@ -38,15 +39,11 @@ export default class ActionHooks {
|
|
|
38
39
|
* Creates a new ActionHook instance.
|
|
39
40
|
*
|
|
40
41
|
* @param {ActionHooksConfig} config Configuration values describing how to load the hooks.
|
|
41
|
-
* @param {(message: string, level?: number, ...args: Array<unknown>) => void} debug Debug function
|
|
42
42
|
*/
|
|
43
|
-
constructor(
|
|
44
|
-
{actionKind, hooksFile, hooksObject, hookTimeout = 1_000},
|
|
45
|
-
debug,
|
|
46
|
-
) {
|
|
43
|
+
constructor({actionKind, hooksFile, hooks, hookTimeout = 1_000, debug}) {
|
|
47
44
|
this.#actionKind = actionKind
|
|
48
45
|
this.#hooksFile = hooksFile
|
|
49
|
-
this.#
|
|
46
|
+
this.#hooks = hooks
|
|
50
47
|
this.#timeout = hookTimeout
|
|
51
48
|
this.#debug = debug
|
|
52
49
|
}
|
|
@@ -54,7 +51,7 @@ export default class ActionHooks {
|
|
|
54
51
|
/**
|
|
55
52
|
* Gets the action identifier.
|
|
56
53
|
*
|
|
57
|
-
* @returns {string
|
|
54
|
+
* @returns {string} Action identifier or instance
|
|
58
55
|
*/
|
|
59
56
|
get actionKind() {
|
|
60
57
|
return this.#actionKind
|
|
@@ -63,7 +60,7 @@ export default class ActionHooks {
|
|
|
63
60
|
/**
|
|
64
61
|
* Gets the hooks file object.
|
|
65
62
|
*
|
|
66
|
-
* @returns {FileObject
|
|
63
|
+
* @returns {FileObject} File object containing hooks
|
|
67
64
|
*/
|
|
68
65
|
get hooksFile() {
|
|
69
66
|
return this.#hooksFile
|
|
@@ -72,10 +69,10 @@ export default class ActionHooks {
|
|
|
72
69
|
/**
|
|
73
70
|
* Gets the loaded hooks object.
|
|
74
71
|
*
|
|
75
|
-
* @returns {
|
|
72
|
+
* @returns {object|null} Hooks object or null if not loaded
|
|
76
73
|
*/
|
|
77
74
|
get hooks() {
|
|
78
|
-
return this.#
|
|
75
|
+
return this.#hooks
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
/**
|
|
@@ -108,17 +105,17 @@ export default class ActionHooks {
|
|
|
108
105
|
/**
|
|
109
106
|
* Static factory method to create and initialize a hook manager.
|
|
110
107
|
* Loads hooks from the specified file and returns an initialized instance.
|
|
111
|
-
*
|
|
108
|
+
* Override loadHooks() in subclasses to customize hook loading logic.
|
|
112
109
|
*
|
|
113
|
-
* @param {ActionHooksConfig} config
|
|
110
|
+
* @param {ActionHooksConfig} config Same configuration object as constructor
|
|
114
111
|
* @param {DebugFn} debug The debug function.
|
|
115
|
-
* @returns {Promise<ActionHooks>} Initialized hook manager
|
|
112
|
+
* @returns {Promise<ActionHooks|null>} Initialized hook manager or null if no hooks found
|
|
116
113
|
*/
|
|
117
114
|
static async new(config, debug) {
|
|
118
115
|
debug("Creating new HookManager instance with args: %o", 2, config)
|
|
119
116
|
|
|
120
117
|
const instance = new ActionHooks(config, debug)
|
|
121
|
-
if(!instance.#
|
|
118
|
+
if(!instance.#hooks) {
|
|
122
119
|
const hooksFile = new FileObject(instance.#hooksFile)
|
|
123
120
|
|
|
124
121
|
debug("Loading hooks from %o", 2, hooksFile.uri)
|
|
@@ -143,7 +140,7 @@ export default class ActionHooks {
|
|
|
143
140
|
|
|
144
141
|
debug(hooks.constructor.name, 4)
|
|
145
142
|
|
|
146
|
-
instance.#
|
|
143
|
+
instance.#hooks = hooks
|
|
147
144
|
debug("Hooks %o loaded successfully for %o", 2, hooksFile.uri, instance.actionKind)
|
|
148
145
|
|
|
149
146
|
return instance
|
|
@@ -154,34 +151,31 @@ export default class ActionHooks {
|
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
153
|
|
|
157
|
-
return
|
|
154
|
+
return this
|
|
158
155
|
}
|
|
159
156
|
|
|
160
157
|
/**
|
|
161
|
-
* Invoke a dynamically-named hook such as `before$foo
|
|
162
|
-
* The hook name is constructed by combining the kind with the activity name.
|
|
163
|
-
* Symbols are converted to their description. Non-alphanumeric characters are filtered out.
|
|
158
|
+
* Invoke a dynamically-named hook such as `before$foo`.
|
|
164
159
|
*
|
|
165
160
|
* @param {'before'|'after'|'setup'|'cleanup'|string} kind Hook namespace.
|
|
166
161
|
* @param {string|symbol} activityName Activity identifier.
|
|
167
162
|
* @param {unknown} context Pipeline context supplied to the hook.
|
|
168
163
|
* @returns {Promise<void>}
|
|
169
|
-
* @throws {Sass} If the hook execution fails or exceeds timeout.
|
|
170
164
|
*/
|
|
171
165
|
async callHook(kind, activityName, context) {
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
try {
|
|
167
|
+
const debug = this.#debug
|
|
168
|
+
const hooks = this.#hooks
|
|
174
169
|
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
if(!hooks)
|
|
171
|
+
return
|
|
177
172
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
173
|
+
const stringActivityName = Data.isType("Symbol")
|
|
174
|
+
? activityName.description()
|
|
175
|
+
: activityName
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
const hookName = this.#getActivityHookName(kind, stringActivityName)
|
|
183
178
|
|
|
184
|
-
try {
|
|
185
179
|
debug("Looking for hook: %o", 4, hookName)
|
|
186
180
|
|
|
187
181
|
const hook = hooks[hookName]
|
|
@@ -195,7 +189,7 @@ export default class ActionHooks {
|
|
|
195
189
|
debug("Hook function starting execution: %o", 4, hookName)
|
|
196
190
|
|
|
197
191
|
const duration = (
|
|
198
|
-
await Util.time(() => hook.call(this.#
|
|
192
|
+
await Util.time(() => hook.call(this.#hooks, context))
|
|
199
193
|
).cost
|
|
200
194
|
|
|
201
195
|
debug("Hook function completed successfully: %o, after %oms", 4, hookName, duration)
|
|
@@ -214,26 +208,16 @@ export default class ActionHooks {
|
|
|
214
208
|
expireAsync
|
|
215
209
|
])
|
|
216
210
|
} catch(error) {
|
|
217
|
-
throw Sass.new(`Processing hook ${
|
|
211
|
+
throw Sass.new(`Processing hook ${kind}$${activityName}`, error)
|
|
218
212
|
}
|
|
219
213
|
|
|
220
214
|
debug("We made it throoough the wildernessss", 4)
|
|
221
215
|
|
|
222
216
|
} catch(error) {
|
|
223
|
-
throw Sass.new(`Processing hook ${
|
|
217
|
+
throw Sass.new(`Processing hook ${kind}$${activityName}`, error)
|
|
224
218
|
}
|
|
225
219
|
}
|
|
226
220
|
|
|
227
|
-
/**
|
|
228
|
-
* Transforms an activity name into a hook-compatible name.
|
|
229
|
-
* Converts "my activity name" to "myActivityName" and combines with event kind.
|
|
230
|
-
* Example: ("before", "my activity") => "before$myActivity"
|
|
231
|
-
*
|
|
232
|
-
* @param {string} event Hook event type (before, after, etc.)
|
|
233
|
-
* @param {string} activityName The raw activity name
|
|
234
|
-
* @returns {string} The formatted hook name
|
|
235
|
-
* @private
|
|
236
|
-
*/
|
|
237
221
|
#getActivityHookName(event, activityName) {
|
|
238
222
|
const name = activityName
|
|
239
223
|
.split(" ")
|
package/src/lib/ActionRunner.js
CHANGED
|
@@ -109,7 +109,7 @@ export default class ActionRunner extends Piper {
|
|
|
109
109
|
context = await this.#execute(activity,context)
|
|
110
110
|
|
|
111
111
|
if(kindUntil)
|
|
112
|
-
if(
|
|
112
|
+
if(await this.#hasPredicate(activity,predicate,context))
|
|
113
113
|
break
|
|
114
114
|
}
|
|
115
115
|
} else if(kindSplit && activity.opKind === "ActionBuilder") {
|
package/src/lib/ActionWrapper.js
CHANGED
|
@@ -3,11 +3,9 @@ import Activity from "./Activity.js"
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {object} WrappedActivityConfig
|
|
5
5
|
* @property {string|symbol} name Activity identifier used by hooks/logs.
|
|
6
|
-
* @property {(context: unknown) => unknown|Promise<unknown>|
|
|
6
|
+
* @property {(context: unknown) => unknown|Promise<unknown>|ActionWrapper} op Operation or nested wrapper to execute.
|
|
7
7
|
* @property {number} [kind] Optional loop semantic flags.
|
|
8
8
|
* @property {(context: unknown) => boolean|Promise<boolean>} [pred] Predicate tied to WHILE/UNTIL semantics.
|
|
9
|
-
* @property {(context: unknown) => unknown} [splitter] Splitter function for SPLIT activities.
|
|
10
|
-
* @property {(originalContext: unknown, splitResults: unknown) => unknown} [rejoiner] Rejoiner function for SPLIT activities.
|
|
11
9
|
* @property {unknown} [action] Parent action instance supplied when invoking the op.
|
|
12
10
|
* @property {(message: string, level?: number, ...args: Array<unknown>) => void} [debug] Optional logger reference.
|
|
13
11
|
*/
|
|
@@ -33,29 +31,21 @@ export default class ActionWrapper {
|
|
|
33
31
|
*/
|
|
34
32
|
#debug = () => {}
|
|
35
33
|
|
|
36
|
-
/**
|
|
37
|
-
* ActionHooks instance shared across all activities.
|
|
38
|
-
*
|
|
39
|
-
* @type {import("./ActionHooks.js").default|null}
|
|
40
|
-
*/
|
|
41
34
|
#hooks = null
|
|
42
35
|
|
|
43
36
|
/**
|
|
44
37
|
* Create a wrapper from the builder payload.
|
|
45
38
|
*
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {Map<string|symbol, WrappedActivityConfig>} config.activities Activities map
|
|
48
|
-
* @param {(message: string, level?: number, ...args: Array<unknown>) => void} config.debug Debug function
|
|
49
|
-
* @param {object} config.hooks Hooks object
|
|
39
|
+
* @param {{activities: Map<string|symbol, WrappedActivityConfig>, debug: (message: string, level?: number, ...args: Array<unknown>) => void}} init Builder payload containing activities + logger.
|
|
50
40
|
*/
|
|
51
|
-
constructor(
|
|
52
|
-
this.#debug =
|
|
53
|
-
this.#hooks =
|
|
54
|
-
this.#activities =
|
|
41
|
+
constructor({activities,hooks,debug}) {
|
|
42
|
+
this.#debug = debug
|
|
43
|
+
this.#hooks = hooks
|
|
44
|
+
this.#activities = activities
|
|
55
45
|
this.#debug(
|
|
56
46
|
"Instantiating ActionWrapper with %o activities.",
|
|
57
47
|
2,
|
|
58
|
-
|
|
48
|
+
activities.size,
|
|
59
49
|
)
|
|
60
50
|
}
|
|
61
51
|
|
package/src/lib/Activity.js
CHANGED
|
@@ -9,7 +9,7 @@ import {Data} from "@gesslar/toolkit"
|
|
|
9
9
|
* @readonly
|
|
10
10
|
* @enum {number}
|
|
11
11
|
* @property {number} WHILE - Execute activity while predicate returns true (2)
|
|
12
|
-
* @property {number} UNTIL - Execute activity until predicate returns
|
|
12
|
+
* @property {number} UNTIL - Execute activity until predicate returns true (4)
|
|
13
13
|
* @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution (8)
|
|
14
14
|
*/
|
|
15
15
|
export const ACTIVITY = Object.freeze({
|