@gesslar/actioneer 0.2.2 → 0.2.4
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 +11 -2
- package/package.json +1 -1
- package/src/lib/ActionBuilder.js +44 -3
- package/src/lib/ActionHooks.js +4 -4
- package/src/lib/ActionRunner.js +130 -42
- package/src/lib/Activity.js +74 -12
- package/src/types/lib/ActionBuilder.d.ts +1 -1
package/README.md
CHANGED
|
@@ -335,13 +335,22 @@ Examples of minimal configs and one-liners to run them are in the project discus
|
|
|
335
335
|
|
|
336
336
|
## Testing
|
|
337
337
|
|
|
338
|
-
Run the
|
|
338
|
+
Run the comprehensive test suite with Node's built-in test runner:
|
|
339
339
|
|
|
340
340
|
```bash
|
|
341
341
|
npm test
|
|
342
342
|
```
|
|
343
343
|
|
|
344
|
-
The test suite
|
|
344
|
+
The test suite includes 150+ tests covering all core classes and behaviors:
|
|
345
|
+
|
|
346
|
+
- **Activity** - Activity definitions, ACTIVITY flags (WHILE, UNTIL, SPLIT), and execution
|
|
347
|
+
- **ActionBuilder** - Fluent builder API, activity registration, and hooks configuration
|
|
348
|
+
- **ActionWrapper** - Activity iteration and integration with ActionBuilder
|
|
349
|
+
- **ActionRunner** - Pipeline execution, loop semantics, nested builders, and error handling
|
|
350
|
+
- **ActionHooks** - Hook lifecycle, loading from files, and timeout handling
|
|
351
|
+
- **Piper** - Concurrent processing, worker pools, and lifecycle hooks
|
|
352
|
+
|
|
353
|
+
Tests are organized in `tests/unit/` with one file per class. All tests use Node's native test runner and assertion library.
|
|
345
354
|
|
|
346
355
|
## Publishing
|
|
347
356
|
|
package/package.json
CHANGED
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 */
|
|
@@ -90,12 +91,17 @@ export default class ActionBuilder {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
get tag() {
|
|
95
|
+
return this.#tag
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
/**
|
|
94
99
|
* Register an activity that the runner can execute.
|
|
95
100
|
*
|
|
96
101
|
* Overloads:
|
|
97
102
|
* - do(name, op)
|
|
98
103
|
* - do(name, kind, pred, opOrWrapper)
|
|
104
|
+
* - do(name, kind, splitter, rejoiner, opOrWrapper)
|
|
99
105
|
*
|
|
100
106
|
* @overload
|
|
101
107
|
* @param {string|symbol} name Activity name
|
|
@@ -112,6 +118,16 @@ export default class ActionBuilder {
|
|
|
112
118
|
* @returns {ActionBuilder}
|
|
113
119
|
*/
|
|
114
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
|
+
|
|
115
131
|
/**
|
|
116
132
|
* Handles runtime dispatch across the documented overloads.
|
|
117
133
|
*
|
|
@@ -124,7 +140,8 @@ export default class ActionBuilder {
|
|
|
124
140
|
|
|
125
141
|
// signatures
|
|
126
142
|
// name, [function] => once
|
|
127
|
-
// 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
|
|
128
145
|
// name, [number,function,ActionBuilder] => some kind of branch
|
|
129
146
|
|
|
130
147
|
const action = this.#action
|
|
@@ -145,6 +162,19 @@ export default class ActionBuilder {
|
|
|
145
162
|
Valid.type(op, "Function|ActionBuilder")
|
|
146
163
|
|
|
147
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})
|
|
148
178
|
} else {
|
|
149
179
|
throw Sass.new("Invalid number of arguments passed to 'do'")
|
|
150
180
|
}
|
|
@@ -178,9 +208,14 @@ export default class ActionBuilder {
|
|
|
178
208
|
*
|
|
179
209
|
* @param {import("./ActionHooks.js").default} hooks An already-instantiated hooks instance.
|
|
180
210
|
* @returns {ActionBuilder} The builder instance for chaining.
|
|
181
|
-
* @throws {Sass} If hooks have already been configured.
|
|
211
|
+
* @throws {Sass} If hooks have already been configured with a different instance.
|
|
182
212
|
*/
|
|
183
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
|
+
|
|
184
219
|
Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
|
|
185
220
|
Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
|
|
186
221
|
Valid.assert(this.#hooks === null, "Hooks have already been configured.")
|
|
@@ -232,8 +267,14 @@ export default class ActionBuilder {
|
|
|
232
267
|
const newHooks = ActionHooks.new
|
|
233
268
|
|
|
234
269
|
const hooks = this.#hooks
|
|
235
|
-
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
|
|
236
276
|
return await newHooks({hooks}, this.#debug)
|
|
277
|
+
}
|
|
237
278
|
|
|
238
279
|
const hooksFile = this.#hooksFile
|
|
239
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 {Sass, Valid} from "@gesslar/toolkit"
|
|
1
|
+
import {Data, Sass, Valid} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
3
|
import ActionBuilder from "./ActionBuilder.js"
|
|
4
4
|
import {ACTIVITY} from "./Activity.js"
|
|
@@ -20,7 +20,10 @@ import Piper from "./Piper.js"
|
|
|
20
20
|
* context object under `result.value` that can be replaced or enriched.
|
|
21
21
|
*/
|
|
22
22
|
export default class ActionRunner extends Piper {
|
|
23
|
+
/** @type {import("./ActionBuilder.js").default|null} */
|
|
23
24
|
#actionBuilder = null
|
|
25
|
+
/** @type {import("./ActionWrapper.js").default|null} */
|
|
26
|
+
#actionWrapper = null
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Logger invoked for diagnostics.
|
|
@@ -48,82 +51,167 @@ export default class ActionRunner extends Piper {
|
|
|
48
51
|
|
|
49
52
|
this.#actionBuilder = actionBuilder
|
|
50
53
|
|
|
51
|
-
this.addStep(this.run
|
|
54
|
+
this.addStep(this.run, {
|
|
55
|
+
name: `ActionRunner for ${actionBuilder.tag.description}`
|
|
56
|
+
})
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
55
60
|
* Executes the configured action pipeline.
|
|
61
|
+
* Builds the ActionWrapper on first run and caches it for subsequent calls.
|
|
62
|
+
* Supports WHILE, UNTIL, and SPLIT activity kinds.
|
|
56
63
|
*
|
|
57
64
|
* @param {unknown} context - Seed value passed to the first activity.
|
|
58
|
-
* @returns {Promise<unknown>} Final value produced by the pipeline
|
|
59
|
-
* @throws {Sass} When no activities are registered
|
|
65
|
+
* @returns {Promise<unknown>} Final value produced by the pipeline.
|
|
66
|
+
* @throws {Sass} When no activities are registered, conflicting activity kinds are used, or execution fails.
|
|
60
67
|
*/
|
|
61
68
|
async run(context) {
|
|
62
|
-
|
|
69
|
+
if(!this.#actionWrapper)
|
|
70
|
+
this.#actionWrapper = await this.#actionBuilder.build()
|
|
71
|
+
|
|
72
|
+
const actionWrapper = this.#actionWrapper
|
|
63
73
|
const activities = actionWrapper.activities
|
|
64
74
|
|
|
65
75
|
for(const activity of activities) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// If we have no kind, then it's just a once.
|
|
69
|
-
// Get it over and done with!
|
|
70
|
-
if(!kind) {
|
|
71
|
-
context = await this.#executeActivity(activity, context)
|
|
72
|
-
} else {
|
|
73
|
-
const {WHILE,UNTIL} = ACTIVITY
|
|
74
|
-
const pred = activity.pred
|
|
75
|
-
const kindWhile = kind & WHILE
|
|
76
|
-
const kindUntil = kind & UNTIL
|
|
77
|
-
|
|
78
|
-
if(kindWhile && kindUntil)
|
|
79
|
-
throw Sass.new(
|
|
80
|
-
"For Kathy Griffin's sake! You can't do something while AND " +
|
|
81
|
-
"until. Pick one!"
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
if(kindWhile || kindUntil) {
|
|
85
|
-
for(;;) {
|
|
86
|
-
|
|
87
|
-
if(kindWhile)
|
|
88
|
-
if(!await this.#predicateCheck(activity,pred,context))
|
|
89
|
-
break
|
|
76
|
+
try {
|
|
77
|
+
// await timeout(500)
|
|
90
78
|
|
|
91
|
-
|
|
79
|
+
const kind = activity.kind
|
|
92
80
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
81
|
+
// If we have no kind, then it's just a once.
|
|
82
|
+
// Get it over and done with!
|
|
83
|
+
if(!kind) {
|
|
84
|
+
context = await this.#execute(activity, context)
|
|
97
85
|
} else {
|
|
98
|
-
|
|
86
|
+
// Validate that only one activity kind bit is set
|
|
87
|
+
// (kind & (kind - 1)) !== 0 means multiple bits are set
|
|
88
|
+
const multipleBitsSet = (kind & (kind - 1)) !== 0
|
|
89
|
+
if(multipleBitsSet)
|
|
90
|
+
throw Sass.new(
|
|
91
|
+
"For Kathy Griffin's sake! You can't combine activity kinds. " +
|
|
92
|
+
"Pick one: WHILE, UNTIL, or SPLIT!"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const {WHILE,UNTIL,SPLIT} = ACTIVITY
|
|
96
|
+
const kindWhile = kind & WHILE
|
|
97
|
+
const kindUntil = kind & UNTIL
|
|
98
|
+
const kindSplit = kind & SPLIT
|
|
99
|
+
|
|
100
|
+
if(kindWhile || kindUntil) {
|
|
101
|
+
const predicate = activity.pred
|
|
102
|
+
|
|
103
|
+
for(;;) {
|
|
104
|
+
|
|
105
|
+
if(kindWhile)
|
|
106
|
+
if(!await this.#hasPredicate(activity,predicate,context))
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
context = await this.#execute(activity,context)
|
|
110
|
+
|
|
111
|
+
if(kindUntil)
|
|
112
|
+
if(await this.#hasPredicate(activity,predicate,context))
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
} else if(kindSplit) {
|
|
116
|
+
// SPLIT activity: parallel execution with splitter/rejoiner pattern
|
|
117
|
+
const splitter = activity.splitter
|
|
118
|
+
const rejoiner = activity.rejoiner
|
|
119
|
+
|
|
120
|
+
if(!splitter || !rejoiner)
|
|
121
|
+
throw Sass.new(
|
|
122
|
+
`SPLIT activity "${String(activity.name)}" requires both splitter and rejoiner functions.`
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const original = context
|
|
126
|
+
const splitContexts = splitter.call(activity.action,context)
|
|
127
|
+
|
|
128
|
+
let newContext
|
|
129
|
+
if(activity.opKind === "ActionBuilder") {
|
|
130
|
+
// Use parallel execution for ActionBuilder
|
|
131
|
+
newContext = await this.#execute(activity,splitContexts,true)
|
|
132
|
+
} else {
|
|
133
|
+
// For plain functions, process each split context
|
|
134
|
+
newContext = await Promise.all(
|
|
135
|
+
splitContexts.map(ctx => this.#execute(activity,ctx))
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const rejoined = rejoiner.call(activity.action, original,newContext)
|
|
140
|
+
|
|
141
|
+
context = rejoined
|
|
142
|
+
} else {
|
|
143
|
+
context = await this.#execute(activity, context)
|
|
144
|
+
}
|
|
99
145
|
}
|
|
146
|
+
} catch(error) {
|
|
147
|
+
throw Sass.new("ActionRunner running activity", error)
|
|
100
148
|
}
|
|
101
|
-
|
|
102
149
|
}
|
|
103
150
|
|
|
104
151
|
return context
|
|
105
152
|
}
|
|
106
153
|
|
|
107
154
|
/**
|
|
108
|
-
* Execute a single activity, recursing into nested
|
|
155
|
+
* Execute a single activity, recursing into nested ActionBuilders when needed.
|
|
156
|
+
* Handles both function-based activities and ActionBuilder-based nested pipelines.
|
|
157
|
+
* Automatically propagates hooks to nested builders and handles dynamic ActionBuilder returns.
|
|
158
|
+
*
|
|
159
|
+
* When parallel=true, uses Piper.pipe() for concurrent execution with worker pool pattern.
|
|
160
|
+
* This is triggered by SPLIT activities where context is divided for parallel processing.
|
|
161
|
+
* Results from parallel execution are returned directly as an array from Piper.pipe().
|
|
109
162
|
*
|
|
110
163
|
* @param {import("./Activity.js").default} activity Pipeline activity descriptor.
|
|
111
164
|
* @param {unknown} context Current pipeline context.
|
|
165
|
+
* @param {boolean} [parallel] Whether to use parallel execution (via pipe() instead of run()). Default: false.
|
|
112
166
|
* @returns {Promise<unknown>} Resolved activity result.
|
|
167
|
+
* @throws {Sass} If the operation kind is invalid, or if SPLIT activity lacks splitter/rejoiner.
|
|
113
168
|
* @private
|
|
114
169
|
*/
|
|
115
|
-
async #
|
|
170
|
+
async #execute(activity, context, parallel=false) {
|
|
116
171
|
// What kind of op are we looking at? Is it a function?
|
|
117
172
|
// Or a class instance of type ActionBuilder?
|
|
118
173
|
const opKind = activity.opKind
|
|
174
|
+
|
|
119
175
|
if(opKind === "ActionBuilder") {
|
|
120
|
-
|
|
176
|
+
if(activity.hooks)
|
|
177
|
+
activity.op.withHooks(activity.hooks)
|
|
121
178
|
|
|
122
|
-
|
|
179
|
+
const runner = new this.constructor(activity.op, {
|
|
180
|
+
debug: this.#debug, name: activity.name
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
if(parallel) {
|
|
184
|
+
return await runner.pipe(context)
|
|
185
|
+
} else {
|
|
186
|
+
return await runner.run(context)
|
|
187
|
+
}
|
|
123
188
|
} else if(opKind === "Function") {
|
|
124
|
-
|
|
189
|
+
try {
|
|
190
|
+
const result = await activity.run(context)
|
|
191
|
+
|
|
192
|
+
if(Data.isType(result, "ActionBuilder")) {
|
|
193
|
+
if(activity.hooks)
|
|
194
|
+
result.withHooks(activity.hooks)
|
|
195
|
+
|
|
196
|
+
const runner = new this.constructor(result, {
|
|
197
|
+
debug: this.#debug, name: result.name
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
if(parallel) {
|
|
201
|
+
return await runner.pipe(context)
|
|
202
|
+
} else {
|
|
203
|
+
return await runner.run(context)
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
} catch(error) {
|
|
209
|
+
throw Sass.new("Executing activity", error)
|
|
210
|
+
}
|
|
125
211
|
}
|
|
126
212
|
|
|
213
|
+
console.log(activity.opKind + " " + JSON.stringify(activity))
|
|
214
|
+
|
|
127
215
|
throw Sass.new("We buy Functions and ActionBuilders. Only. Not whatever that was.")
|
|
128
216
|
}
|
|
129
217
|
|
|
@@ -136,7 +224,7 @@ export default class ActionRunner extends Piper {
|
|
|
136
224
|
* @returns {Promise<boolean>} True when the predicate allows another iteration.
|
|
137
225
|
* @private
|
|
138
226
|
*/
|
|
139
|
-
async #
|
|
227
|
+
async #hasPredicate(activity,predicate,context) {
|
|
140
228
|
Valid.type(predicate, "Function")
|
|
141
229
|
|
|
142
230
|
return !!(await predicate.call(activity.action, context))
|
package/src/lib/Activity.js
CHANGED
|
@@ -8,32 +8,58 @@ import {Data} from "@gesslar/toolkit"
|
|
|
8
8
|
*
|
|
9
9
|
* @readonly
|
|
10
10
|
* @enum {number}
|
|
11
|
+
* @property {number} WHILE - Execute activity while predicate returns true (2)
|
|
12
|
+
* @property {number} UNTIL - Execute activity until predicate returns true (4)
|
|
13
|
+
* @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution (8)
|
|
11
14
|
*/
|
|
12
15
|
export const ACTIVITY = Object.freeze({
|
|
13
16
|
WHILE: 1<<1,
|
|
14
17
|
UNTIL: 1<<2,
|
|
18
|
+
SPLIT: 1<<3,
|
|
15
19
|
})
|
|
16
20
|
|
|
17
21
|
export default class Activity {
|
|
22
|
+
/** @type {unknown} */
|
|
18
23
|
#action = null
|
|
24
|
+
/** @type {unknown} */
|
|
25
|
+
#context = null
|
|
26
|
+
/** @type {ActionHooks|null} */
|
|
27
|
+
#hooks = null
|
|
28
|
+
/** @type {number|null} */
|
|
29
|
+
#kind = null
|
|
30
|
+
/** @type {string|symbol} */
|
|
19
31
|
#name = null
|
|
32
|
+
/** @type {((context: unknown) => unknown|Promise<unknown>)|import("./ActionBuilder.js").default} */
|
|
20
33
|
#op = null
|
|
21
|
-
|
|
34
|
+
/** @type {((context: unknown) => boolean|Promise<boolean>)|null} */
|
|
22
35
|
#pred = null
|
|
23
|
-
|
|
36
|
+
/** @type {((originalContext: unknown, splitResults: unknown) => unknown)|null} */
|
|
37
|
+
#rejoiner = null
|
|
38
|
+
/** @type {((context: unknown) => unknown)|null} */
|
|
39
|
+
#splitter = null
|
|
24
40
|
|
|
25
41
|
/**
|
|
26
42
|
* Construct an Activity definition wrapper.
|
|
27
43
|
*
|
|
28
|
-
* @param {
|
|
44
|
+
* @param {object} init - Initial properties describing the activity operation, loop semantics, and predicate
|
|
45
|
+
* @param {unknown} init.action - Parent action instance
|
|
46
|
+
* @param {string|symbol} init.name - Activity identifier
|
|
47
|
+
* @param {(context: unknown) => unknown|Promise<unknown>|import("./ActionBuilder.js").default} init.op - Operation to execute
|
|
48
|
+
* @param {number} [init.kind] - Optional loop semantics flags
|
|
49
|
+
* @param {(context: unknown) => boolean|Promise<boolean>} [init.pred] - Optional predicate for WHILE/UNTIL
|
|
50
|
+
* @param {ActionHooks} [init.hooks] - Optional hooks instance
|
|
51
|
+
* @param {(context: unknown) => unknown} [init.splitter] - Optional splitter function for SPLIT activities
|
|
52
|
+
* @param {(originalContext: unknown, splitResults: unknown) => unknown} [init.rejoiner] - Optional rejoiner function for SPLIT activities
|
|
29
53
|
*/
|
|
30
|
-
constructor({action,name,op,kind,pred,hooks}) {
|
|
54
|
+
constructor({action,name,op,kind,pred,hooks,splitter,rejoiner}) {
|
|
55
|
+
this.#action = action
|
|
56
|
+
this.#hooks = hooks
|
|
57
|
+
this.#kind = kind
|
|
31
58
|
this.#name = name
|
|
32
59
|
this.#op = op
|
|
33
|
-
this.#kind = kind
|
|
34
|
-
this.#action = action
|
|
35
60
|
this.#pred = pred
|
|
36
|
-
this.#
|
|
61
|
+
this.#rejoiner = rejoiner
|
|
62
|
+
this.#splitter = splitter
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
/**
|
|
@@ -64,7 +90,16 @@ export default class Activity {
|
|
|
64
90
|
}
|
|
65
91
|
|
|
66
92
|
/**
|
|
67
|
-
* The
|
|
93
|
+
* The current context (if set).
|
|
94
|
+
*
|
|
95
|
+
* @returns {unknown} Current context value
|
|
96
|
+
*/
|
|
97
|
+
get context() {
|
|
98
|
+
return this.#context
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The operator kind name (Function or ActionBuilder).
|
|
68
103
|
*
|
|
69
104
|
* @returns {string} - Kind name extracted via Data.typeOf
|
|
70
105
|
*/
|
|
@@ -73,14 +108,32 @@ export default class Activity {
|
|
|
73
108
|
}
|
|
74
109
|
|
|
75
110
|
/**
|
|
76
|
-
* The operator to execute (function or nested
|
|
111
|
+
* The operator to execute (function or nested ActionBuilder).
|
|
77
112
|
*
|
|
78
|
-
* @returns {unknown} - Activity operation
|
|
113
|
+
* @returns {(context: unknown) => unknown|Promise<unknown>|import("./ActionBuilder.js").default} - Activity operation
|
|
79
114
|
*/
|
|
80
115
|
get op() {
|
|
81
116
|
return this.#op
|
|
82
117
|
}
|
|
83
118
|
|
|
119
|
+
/**
|
|
120
|
+
* The splitter function for SPLIT activities.
|
|
121
|
+
*
|
|
122
|
+
* @returns {((context: unknown) => unknown)|null} Splitter function or null
|
|
123
|
+
*/
|
|
124
|
+
get splitter() {
|
|
125
|
+
return this.#splitter
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The rejoiner function for SPLIT activities.
|
|
130
|
+
*
|
|
131
|
+
* @returns {((originalContext: unknown, splitResults: unknown) => unknown)|null} Rejoiner function or null
|
|
132
|
+
*/
|
|
133
|
+
get rejoiner() {
|
|
134
|
+
return this.#rejoiner
|
|
135
|
+
}
|
|
136
|
+
|
|
84
137
|
/**
|
|
85
138
|
* The action instance this activity belongs to.
|
|
86
139
|
*
|
|
@@ -94,7 +147,7 @@ export default class Activity {
|
|
|
94
147
|
* Execute the activity with before/after hooks.
|
|
95
148
|
*
|
|
96
149
|
* @param {unknown} context - Mutable context flowing through the pipeline
|
|
97
|
-
* @returns {Promise<
|
|
150
|
+
* @returns {Promise<unknown>} - Activity result
|
|
98
151
|
*/
|
|
99
152
|
async run(context) {
|
|
100
153
|
// before hook
|
|
@@ -112,7 +165,7 @@ export default class Activity {
|
|
|
112
165
|
/**
|
|
113
166
|
* Attach hooks to this activity instance.
|
|
114
167
|
*
|
|
115
|
-
* @param {
|
|
168
|
+
* @param {ActionHooks} hooks - Hooks instance with optional before$/after$ methods
|
|
116
169
|
* @returns {this} - This activity for chaining
|
|
117
170
|
*/
|
|
118
171
|
setActionHooks(hooks) {
|
|
@@ -121,4 +174,13 @@ export default class Activity {
|
|
|
121
174
|
|
|
122
175
|
return this
|
|
123
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the hooks instance attached to this activity.
|
|
180
|
+
*
|
|
181
|
+
* @returns {ActionHooks|null} The hooks instance or null
|
|
182
|
+
*/
|
|
183
|
+
get hooks() {
|
|
184
|
+
return this.#hooks
|
|
185
|
+
}
|
|
124
186
|
}
|
|
@@ -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
|
/**
|