@gesslar/actioneer 0.1.3 → 0.2.1
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 +128 -2
- package/package.json +1 -1
- package/src/lib/ActionBuilder.js +158 -16
- package/src/lib/ActionHooks.js +90 -50
- package/src/lib/ActionRunner.js +131 -138
- package/src/lib/ActionWrapper.js +20 -7
- package/src/lib/Activity.js +79 -19
- package/src/lib/Piper.js +106 -57
- package/src/types/lib/ActionBuilder.d.ts +63 -6
- package/src/types/lib/ActionBuilder.d.ts.map +1 -1
- package/src/types/lib/ActionHooks.d.ts +23 -24
- package/src/types/lib/ActionHooks.d.ts.map +1 -1
- package/src/types/lib/ActionRunner.d.ts +7 -20
- package/src/types/lib/ActionRunner.d.ts.map +1 -1
- package/src/types/lib/ActionWrapper.d.ts +19 -5
- package/src/types/lib/ActionWrapper.d.ts.map +1 -1
- package/src/types/lib/Activity.d.ts +56 -18
- package/src/types/lib/Activity.d.ts.map +1 -1
- package/src/types/lib/Piper.d.ts +24 -7
- package/src/types/lib/Piper.d.ts.map +1 -1
package/src/lib/ActionRunner.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Data, Sass, Valid} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
3
|
import ActionBuilder from "./ActionBuilder.js"
|
|
4
4
|
import {ACTIVITY} from "./Activity.js"
|
|
5
5
|
import Piper from "./Piper.js"
|
|
6
6
|
|
|
7
|
-
/** @typedef {import("./ActionHooks.js").default} ActionHooks */
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* @typedef {(message: string, level?: number, ...args: Array<unknown>) => void} DebugFn
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* @typedef {object} ActionRunnerOptions
|
|
15
|
-
* @property {ActionHooks} [hooks] Pre-configured hooks.
|
|
16
13
|
* @property {DebugFn} [debug] Logger function.
|
|
17
14
|
*/
|
|
18
15
|
/**
|
|
@@ -23,154 +20,192 @@ import Piper from "./Piper.js"
|
|
|
23
20
|
* context object under `result.value` that can be replaced or enriched.
|
|
24
21
|
*/
|
|
25
22
|
export default class ActionRunner extends Piper {
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* @type {import("./ActionWrapper.js").default|null}
|
|
30
|
-
*/
|
|
23
|
+
/** @type {import("./ActionBuilder.js").default|null} */
|
|
24
|
+
#actionBuilder = null
|
|
25
|
+
/** @type {import("./ActionWrapper.js").default|null} */
|
|
31
26
|
#actionWrapper = null
|
|
27
|
+
|
|
32
28
|
/**
|
|
33
29
|
* Logger invoked for diagnostics.
|
|
34
30
|
*
|
|
35
31
|
* @type {DebugFn}
|
|
36
32
|
*/
|
|
37
33
|
#debug = () => {}
|
|
38
|
-
/**
|
|
39
|
-
* Filesystem path to a hooks module.
|
|
40
|
-
*
|
|
41
|
-
* @type {string|null}
|
|
42
|
-
*/
|
|
43
|
-
#hooksPath = null
|
|
44
|
-
/**
|
|
45
|
-
* Constructor name exported by the hooks module.
|
|
46
|
-
*
|
|
47
|
-
* @type {string|null}
|
|
48
|
-
*/
|
|
49
|
-
#hooksClassName = null
|
|
50
|
-
/**
|
|
51
|
-
* Lazily instantiated hooks implementation.
|
|
52
|
-
*
|
|
53
|
-
* @type {ActionHooks|null}
|
|
54
|
-
*/
|
|
55
|
-
#hooks = null
|
|
56
|
-
/**
|
|
57
|
-
* Unique tag for log correlation.
|
|
58
|
-
*
|
|
59
|
-
* @type {symbol|null}
|
|
60
|
-
*/
|
|
61
|
-
#tag = null
|
|
62
34
|
|
|
63
35
|
/**
|
|
64
36
|
* Instantiate a runner over an optional action wrapper.
|
|
65
37
|
*
|
|
66
|
-
* @param {import("./
|
|
67
|
-
* @param {ActionRunnerOptions} [options] Optional
|
|
38
|
+
* @param {import("./ActionBuilder.js").default|null} actionBuilder ActionBuilder to build.
|
|
39
|
+
* @param {ActionRunnerOptions} [options] Optional debug overrides.
|
|
68
40
|
*/
|
|
69
|
-
constructor(
|
|
41
|
+
constructor(actionBuilder, {debug=(() => {})} = {}) {
|
|
70
42
|
super({debug})
|
|
71
43
|
|
|
72
|
-
this.#tag = Symbol(performance.now())
|
|
73
|
-
|
|
74
44
|
this.#debug = debug
|
|
75
45
|
|
|
76
|
-
if(!
|
|
46
|
+
if(!actionBuilder)
|
|
77
47
|
return this
|
|
78
48
|
|
|
79
|
-
if(
|
|
80
|
-
throw Sass.new("ActionRunner takes an instance of an
|
|
81
|
-
|
|
82
|
-
this.#actionWrapper = wrappedAction
|
|
49
|
+
if(actionBuilder?.constructor?.name !== "ActionBuilder")
|
|
50
|
+
throw Sass.new("ActionRunner takes an instance of an ActionBuilder")
|
|
83
51
|
|
|
84
|
-
|
|
85
|
-
this.#hooks = hooks
|
|
86
|
-
else
|
|
87
|
-
this.addSetup(this.#loadHooks)
|
|
52
|
+
this.#actionBuilder = actionBuilder
|
|
88
53
|
|
|
89
|
-
this.addStep(this.run
|
|
54
|
+
this.addStep(this.run, {
|
|
55
|
+
name: `ActionRunner for ${actionBuilder.tag.description}`
|
|
56
|
+
})
|
|
90
57
|
}
|
|
91
58
|
|
|
92
59
|
/**
|
|
93
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.
|
|
94
63
|
*
|
|
95
64
|
* @param {unknown} context - Seed value passed to the first activity.
|
|
96
|
-
* @returns {Promise<unknown>} Final value produced by the pipeline
|
|
97
|
-
* @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.
|
|
98
67
|
*/
|
|
99
68
|
async run(context) {
|
|
100
|
-
|
|
69
|
+
if(!this.#actionWrapper)
|
|
70
|
+
this.#actionWrapper = await this.#actionBuilder.build()
|
|
71
|
+
|
|
101
72
|
const actionWrapper = this.#actionWrapper
|
|
102
73
|
const activities = actionWrapper.activities
|
|
103
74
|
|
|
104
75
|
for(const activity of activities) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const kind = activity.kind
|
|
108
|
-
|
|
109
|
-
// If we have no kind, then it's just a once.
|
|
110
|
-
// Get it over and done with!
|
|
111
|
-
if(!kind) {
|
|
112
|
-
context = await this.#executeActivity(activity, context)
|
|
113
|
-
} else {
|
|
114
|
-
const {WHILE,UNTIL} = ACTIVITY
|
|
115
|
-
|
|
116
|
-
const pred = activity.pred
|
|
117
|
-
const kindWhile = kind & WHILE
|
|
118
|
-
const kindUntil = kind & UNTIL
|
|
76
|
+
try {
|
|
77
|
+
// await timeout(500)
|
|
119
78
|
|
|
120
|
-
|
|
121
|
-
throw Sass.new(
|
|
122
|
-
"For Kathy Griffin's sake! You can't do something while AND " +
|
|
123
|
-
"until. Pick one!"
|
|
124
|
-
)
|
|
79
|
+
const kind = activity.kind
|
|
125
80
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if(!await this.#predicateCheck(activity,pred,context))
|
|
131
|
-
break
|
|
132
|
-
|
|
133
|
-
context = await this.#executeActivity(activity,context)
|
|
134
|
-
|
|
135
|
-
if(kindUntil)
|
|
136
|
-
if(!await this.#predicateCheck(activity,pred,context))
|
|
137
|
-
break
|
|
138
|
-
}
|
|
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)
|
|
139
85
|
} else {
|
|
140
|
-
|
|
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 && activity.opKind === "ActionBuilder") {
|
|
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 splitContext = splitter.call(activity.action,context)
|
|
127
|
+
const newContext = await this.#execute(activity,splitContext,true)
|
|
128
|
+
const rejoined = rejoiner.call(activity.action, original,newContext)
|
|
129
|
+
|
|
130
|
+
context = rejoined
|
|
131
|
+
} else {
|
|
132
|
+
context = await this.#execute(activity, context)
|
|
133
|
+
}
|
|
141
134
|
}
|
|
135
|
+
} catch(error) {
|
|
136
|
+
throw Sass.new("ActionRunner running activity", error)
|
|
142
137
|
}
|
|
143
|
-
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
return context
|
|
147
141
|
}
|
|
148
142
|
|
|
149
143
|
/**
|
|
150
|
-
* Execute a single activity, recursing into nested
|
|
144
|
+
* Execute a single activity, recursing into nested ActionBuilders when needed.
|
|
145
|
+
* Handles both function-based activities and ActionBuilder-based nested pipelines.
|
|
146
|
+
* Automatically propagates hooks to nested builders and handles dynamic ActionBuilder returns.
|
|
147
|
+
*
|
|
148
|
+
* When parallel=true, uses Piper.pipe() for concurrent execution with worker pool pattern.
|
|
149
|
+
* This is triggered by SPLIT activities where context is divided for parallel processing.
|
|
150
|
+
* Results from parallel execution are filtered to only include successful outcomes ({ok: true}).
|
|
151
151
|
*
|
|
152
152
|
* @param {import("./Activity.js").default} activity Pipeline activity descriptor.
|
|
153
153
|
* @param {unknown} context Current pipeline context.
|
|
154
|
+
* @param {boolean} [parallel] Whether to use parallel execution (via pipe() instead of run()). Default: false.
|
|
154
155
|
* @returns {Promise<unknown>} Resolved activity result.
|
|
156
|
+
* @throws {Sass} If the operation kind is invalid, or if SPLIT activity lacks splitter/rejoiner.
|
|
155
157
|
* @private
|
|
156
158
|
*/
|
|
157
|
-
async #
|
|
159
|
+
async #execute(activity, context, parallel=false) {
|
|
158
160
|
// What kind of op are we looking at? Is it a function?
|
|
159
|
-
// Or a class instance of type
|
|
161
|
+
// Or a class instance of type ActionBuilder?
|
|
160
162
|
const opKind = activity.opKind
|
|
161
|
-
|
|
163
|
+
|
|
164
|
+
if(opKind === "ActionBuilder") {
|
|
165
|
+
if(activity.hooks && !activity.op.hasActionHooks)
|
|
166
|
+
activity.op.withActionHooks(activity.hooks)
|
|
167
|
+
|
|
162
168
|
const runner = new this.constructor(activity.op, {
|
|
163
|
-
debug: this.#debug,
|
|
164
|
-
hooks: this.#hooks,
|
|
169
|
+
debug: this.#debug, name: activity.name
|
|
165
170
|
})
|
|
166
|
-
.setHooks(this.#hooksPath, this.#hooksClassName)
|
|
167
171
|
|
|
168
|
-
|
|
172
|
+
if(parallel) {
|
|
173
|
+
const piped = await runner.pipe(context)
|
|
174
|
+
|
|
175
|
+
return piped.filter(p => p.ok).map(p => p.value)
|
|
176
|
+
} else {
|
|
177
|
+
return await runner.run(context)
|
|
178
|
+
}
|
|
169
179
|
} else if(opKind === "Function") {
|
|
170
|
-
|
|
180
|
+
try {
|
|
181
|
+
const result = await activity.run(context)
|
|
182
|
+
|
|
183
|
+
if(Data.isType(result, "ActionBuilder")) {
|
|
184
|
+
if(activity.hooks)
|
|
185
|
+
result.withActionHooks(activity.hooks)
|
|
186
|
+
|
|
187
|
+
const runner = new this.constructor(result, {
|
|
188
|
+
debug: this.#debug, name: result.name
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
if(parallel) {
|
|
192
|
+
const piped = await runner.pipe(context)
|
|
193
|
+
|
|
194
|
+
return piped.filter(p => p.ok).map(p => p.value)
|
|
195
|
+
} else {
|
|
196
|
+
return await runner.run(context)
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
return result
|
|
200
|
+
}
|
|
201
|
+
} catch(error) {
|
|
202
|
+
throw Sass.new("Executing activity", error)
|
|
203
|
+
}
|
|
171
204
|
}
|
|
172
205
|
|
|
173
|
-
|
|
206
|
+
console.log(activity.opKind + " " + JSON.stringify(activity))
|
|
207
|
+
|
|
208
|
+
throw Sass.new("We buy Functions and ActionBuilders. Only. Not whatever that was.")
|
|
174
209
|
}
|
|
175
210
|
|
|
176
211
|
/**
|
|
@@ -182,7 +217,7 @@ export default class ActionRunner extends Piper {
|
|
|
182
217
|
* @returns {Promise<boolean>} True when the predicate allows another iteration.
|
|
183
218
|
* @private
|
|
184
219
|
*/
|
|
185
|
-
async #
|
|
220
|
+
async #hasPredicate(activity,predicate,context) {
|
|
186
221
|
Valid.type(predicate, "Function")
|
|
187
222
|
|
|
188
223
|
return !!(await predicate.call(activity.action, context))
|
|
@@ -191,46 +226,4 @@ export default class ActionRunner extends Piper {
|
|
|
191
226
|
toString() {
|
|
192
227
|
return `[object ${this.constructor.name}]`
|
|
193
228
|
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Configure hooks to be lazily loaded when the pipeline runs.
|
|
197
|
-
*
|
|
198
|
-
* @param {string} hooksPath Absolute path to the module exporting the hooks class.
|
|
199
|
-
* @param {string} className Constructor to instantiate from the hooks module.
|
|
200
|
-
* @returns {this} Runner instance for chaining.
|
|
201
|
-
*/
|
|
202
|
-
setHooks(hooksPath, className) {
|
|
203
|
-
this.#hooksPath = hooksPath
|
|
204
|
-
this.#hooksClassName = className
|
|
205
|
-
|
|
206
|
-
this.addSetup(() => this.#loadHooks())
|
|
207
|
-
|
|
208
|
-
return this
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Import and instantiate the configured hooks module.
|
|
213
|
-
*
|
|
214
|
-
* @returns {Promise<null|void>} Null when hooks are disabled, otherwise void.
|
|
215
|
-
* @private
|
|
216
|
-
*/
|
|
217
|
-
async #loadHooks() {
|
|
218
|
-
if(!this.#hooksPath)
|
|
219
|
-
return null
|
|
220
|
-
|
|
221
|
-
const file = new FileObject(this.#hooksPath)
|
|
222
|
-
if(!await file.exists)
|
|
223
|
-
throw Sass.new(`File '${file.uri} does not exist.`)
|
|
224
|
-
|
|
225
|
-
const module = await file.import()
|
|
226
|
-
const hooksClassName = this.#hooksClassName
|
|
227
|
-
|
|
228
|
-
Valid.type(module[hooksClassName], "Function")
|
|
229
|
-
|
|
230
|
-
const loaded = new module[hooksClassName]({
|
|
231
|
-
debug: this.#debug
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
this.#hooks = loaded
|
|
235
|
-
}
|
|
236
229
|
}
|
package/src/lib/ActionWrapper.js
CHANGED
|
@@ -3,9 +3,11 @@ 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>|import("./ActionBuilder.js").default} op Operation or nested ActionBuilder 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.
|
|
9
11
|
* @property {unknown} [action] Parent action instance supplied when invoking the op.
|
|
10
12
|
* @property {(message: string, level?: number, ...args: Array<unknown>) => void} [debug] Optional logger reference.
|
|
11
13
|
*/
|
|
@@ -31,24 +33,35 @@ export default class ActionWrapper {
|
|
|
31
33
|
*/
|
|
32
34
|
#debug = () => {}
|
|
33
35
|
|
|
36
|
+
/**
|
|
37
|
+
* ActionHooks instance shared across all activities.
|
|
38
|
+
*
|
|
39
|
+
* @type {import("./ActionHooks.js").default|null}
|
|
40
|
+
*/
|
|
41
|
+
#hooks = null
|
|
42
|
+
|
|
34
43
|
/**
|
|
35
44
|
* Create a wrapper from the builder payload.
|
|
36
45
|
*
|
|
37
|
-
* @param {
|
|
46
|
+
* @param {object} config Builder payload containing activities + logger
|
|
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
|
|
38
50
|
*/
|
|
39
|
-
constructor(
|
|
40
|
-
this.#debug = debug
|
|
41
|
-
this.#
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this.#debug = config.debug
|
|
53
|
+
this.#hooks = config.hooks
|
|
54
|
+
this.#activities = config.activities
|
|
42
55
|
this.#debug(
|
|
43
56
|
"Instantiating ActionWrapper with %o activities.",
|
|
44
57
|
2,
|
|
45
|
-
activities.size,
|
|
58
|
+
this.#activities.size,
|
|
46
59
|
)
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
*#_activities() {
|
|
50
63
|
for(const [,activity] of this.#activities)
|
|
51
|
-
yield new Activity(activity)
|
|
64
|
+
yield new Activity({...activity, hooks: this.#hooks})
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
/**
|
package/src/lib/Activity.js
CHANGED
|
@@ -1,36 +1,65 @@
|
|
|
1
1
|
import {Data} from "@gesslar/toolkit"
|
|
2
2
|
|
|
3
|
+
/** @typedef {import("./ActionHooks.js").default} ActionHooks */
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Activity bit flags recognised by the builder. The flag decides
|
|
5
7
|
* loop semantics for an activity.
|
|
6
8
|
*
|
|
7
9
|
* @readonly
|
|
8
10
|
* @enum {number}
|
|
11
|
+
* @property {number} WHILE - Execute activity while predicate returns true (2)
|
|
12
|
+
* @property {number} UNTIL - Execute activity until predicate returns false (4)
|
|
13
|
+
* @property {number} SPLIT - Execute activity with split/rejoin pattern for parallel execution (8)
|
|
9
14
|
*/
|
|
10
15
|
export const ACTIVITY = Object.freeze({
|
|
11
16
|
WHILE: 1<<1,
|
|
12
17
|
UNTIL: 1<<2,
|
|
18
|
+
SPLIT: 1<<3,
|
|
13
19
|
})
|
|
14
20
|
|
|
15
21
|
export default class Activity {
|
|
22
|
+
/** @type {unknown} */
|
|
16
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} */
|
|
17
31
|
#name = null
|
|
32
|
+
/** @type {((context: unknown) => unknown|Promise<unknown>)|import("./ActionBuilder.js").default} */
|
|
18
33
|
#op = null
|
|
19
|
-
|
|
34
|
+
/** @type {((context: unknown) => boolean|Promise<boolean>)|null} */
|
|
20
35
|
#pred = null
|
|
21
|
-
|
|
36
|
+
/** @type {((originalContext: unknown, splitResults: unknown) => unknown)|null} */
|
|
37
|
+
#rejoiner = null
|
|
38
|
+
/** @type {((context: unknown) => unknown)|null} */
|
|
39
|
+
#splitter = null
|
|
22
40
|
|
|
23
41
|
/**
|
|
24
42
|
* Construct an Activity definition wrapper.
|
|
25
43
|
*
|
|
26
|
-
* @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
|
|
27
53
|
*/
|
|
28
|
-
constructor({action,name,op,kind,pred}) {
|
|
54
|
+
constructor({action,name,op,kind,pred,hooks,splitter,rejoiner}) {
|
|
55
|
+
this.#action = action
|
|
56
|
+
this.#hooks = hooks
|
|
57
|
+
this.#kind = kind
|
|
29
58
|
this.#name = name
|
|
30
59
|
this.#op = op
|
|
31
|
-
this.#kind = kind
|
|
32
|
-
this.#action = action
|
|
33
60
|
this.#pred = pred
|
|
61
|
+
this.#rejoiner = rejoiner
|
|
62
|
+
this.#splitter = splitter
|
|
34
63
|
}
|
|
35
64
|
|
|
36
65
|
/**
|
|
@@ -61,7 +90,16 @@ export default class Activity {
|
|
|
61
90
|
}
|
|
62
91
|
|
|
63
92
|
/**
|
|
64
|
-
* 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).
|
|
65
103
|
*
|
|
66
104
|
* @returns {string} - Kind name extracted via Data.typeOf
|
|
67
105
|
*/
|
|
@@ -70,14 +108,32 @@ export default class Activity {
|
|
|
70
108
|
}
|
|
71
109
|
|
|
72
110
|
/**
|
|
73
|
-
* The operator to execute (function or nested
|
|
111
|
+
* The operator to execute (function or nested ActionBuilder).
|
|
74
112
|
*
|
|
75
|
-
* @returns {unknown} - Activity operation
|
|
113
|
+
* @returns {(context: unknown) => unknown|Promise<unknown>|import("./ActionBuilder.js").default} - Activity operation
|
|
76
114
|
*/
|
|
77
115
|
get op() {
|
|
78
116
|
return this.#op
|
|
79
117
|
}
|
|
80
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
|
+
|
|
81
137
|
/**
|
|
82
138
|
* The action instance this activity belongs to.
|
|
83
139
|
*
|
|
@@ -91,22 +147,17 @@ export default class Activity {
|
|
|
91
147
|
* Execute the activity with before/after hooks.
|
|
92
148
|
*
|
|
93
149
|
* @param {unknown} context - Mutable context flowing through the pipeline
|
|
94
|
-
* @returns {Promise<
|
|
150
|
+
* @returns {Promise<unknown>} - Activity result
|
|
95
151
|
*/
|
|
96
152
|
async run(context) {
|
|
97
|
-
const hooks = this.#hooks
|
|
98
|
-
|
|
99
153
|
// before hook
|
|
100
|
-
|
|
101
|
-
if(Data.typeOf(before) === "Function")
|
|
102
|
-
await before.call(hooks,context)
|
|
154
|
+
await this.#hooks?.callHook("before", this.#name, context)
|
|
103
155
|
|
|
156
|
+
// not a hook
|
|
104
157
|
const result = await this.#op.call(this.#action,context)
|
|
105
158
|
|
|
106
159
|
// after hook
|
|
107
|
-
|
|
108
|
-
if(Data.typeOf(after) === "Function")
|
|
109
|
-
await after.call(hooks,context)
|
|
160
|
+
await this.#hooks?.callHook("after", this.#name, context)
|
|
110
161
|
|
|
111
162
|
return result
|
|
112
163
|
}
|
|
@@ -114,7 +165,7 @@ export default class Activity {
|
|
|
114
165
|
/**
|
|
115
166
|
* Attach hooks to this activity instance.
|
|
116
167
|
*
|
|
117
|
-
* @param {
|
|
168
|
+
* @param {ActionHooks} hooks - Hooks instance with optional before$/after$ methods
|
|
118
169
|
* @returns {this} - This activity for chaining
|
|
119
170
|
*/
|
|
120
171
|
setActionHooks(hooks) {
|
|
@@ -123,4 +174,13 @@ export default class Activity {
|
|
|
123
174
|
|
|
124
175
|
return this
|
|
125
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
|
+
}
|
|
126
186
|
}
|