@gesslar/actioneer 0.2.3 → 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 +40 -3
- package/src/lib/ActionHooks.js +4 -4
- package/src/lib/ActionRunner.js +20 -13
- 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 */
|
|
@@ -100,6 +101,7 @@ export default class ActionBuilder {
|
|
|
100
101
|
* Overloads:
|
|
101
102
|
* - do(name, op)
|
|
102
103
|
* - do(name, kind, pred, opOrWrapper)
|
|
104
|
+
* - do(name, kind, splitter, rejoiner, opOrWrapper)
|
|
103
105
|
*
|
|
104
106
|
* @overload
|
|
105
107
|
* @param {string|symbol} name Activity name
|
|
@@ -116,6 +118,16 @@ export default class ActionBuilder {
|
|
|
116
118
|
* @returns {ActionBuilder}
|
|
117
119
|
*/
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @overload
|
|
123
|
+
* @param {string|symbol} name Activity name
|
|
124
|
+
* @param {number} kind ACTIVITY.SPLIT flag.
|
|
125
|
+
* @param {(context: unknown) => unknown} splitter Splitter function for SPLIT mode.
|
|
126
|
+
* @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Rejoiner function for SPLIT mode.
|
|
127
|
+
* @param {ActionFunction|import("./ActionWrapper.js").default} op Operation or nested wrapper to execute.
|
|
128
|
+
* @returns {ActionBuilder}
|
|
129
|
+
*/
|
|
130
|
+
|
|
119
131
|
/**
|
|
120
132
|
* Handles runtime dispatch across the documented overloads.
|
|
121
133
|
*
|
|
@@ -128,7 +140,8 @@ export default class ActionBuilder {
|
|
|
128
140
|
|
|
129
141
|
// signatures
|
|
130
142
|
// name, [function] => once
|
|
131
|
-
// name, [number,function,function] => some kind of control operation
|
|
143
|
+
// name, [number,function,function] => some kind of control operation (WHILE/UNTIL)
|
|
144
|
+
// name, [number,function,function,function] => SPLIT operation with splitter/rejoiner
|
|
132
145
|
// name, [number,function,ActionBuilder] => some kind of branch
|
|
133
146
|
|
|
134
147
|
const action = this.#action
|
|
@@ -149,6 +162,19 @@ export default class ActionBuilder {
|
|
|
149
162
|
Valid.type(op, "Function|ActionBuilder")
|
|
150
163
|
|
|
151
164
|
Object.assign(activityDefinition, {kind, pred, op})
|
|
165
|
+
} else if(args.length === 4) {
|
|
166
|
+
const [kind, splitter, rejoiner, op] = args
|
|
167
|
+
|
|
168
|
+
Valid.type(kind, "Number")
|
|
169
|
+
Valid.type(splitter, "Function")
|
|
170
|
+
Valid.type(rejoiner, "Function")
|
|
171
|
+
Valid.type(op, "Function|ActionBuilder")
|
|
172
|
+
|
|
173
|
+
// Validate that kind is SPLIT
|
|
174
|
+
if((kind & ACTIVITY.SPLIT) !== ACTIVITY.SPLIT)
|
|
175
|
+
throw Sass.new("4-argument form of 'do' is only valid for ACTIVITY.SPLIT")
|
|
176
|
+
|
|
177
|
+
Object.assign(activityDefinition, {kind, splitter, rejoiner, op})
|
|
152
178
|
} else {
|
|
153
179
|
throw Sass.new("Invalid number of arguments passed to 'do'")
|
|
154
180
|
}
|
|
@@ -182,9 +208,14 @@ export default class ActionBuilder {
|
|
|
182
208
|
*
|
|
183
209
|
* @param {import("./ActionHooks.js").default} hooks An already-instantiated hooks instance.
|
|
184
210
|
* @returns {ActionBuilder} The builder instance for chaining.
|
|
185
|
-
* @throws {Sass} If hooks have already been configured.
|
|
211
|
+
* @throws {Sass} If hooks have already been configured with a different instance.
|
|
186
212
|
*/
|
|
187
213
|
withHooks(hooks) {
|
|
214
|
+
// If the same hooks instance is already set, this is idempotent - just return
|
|
215
|
+
if(this.#hooks === hooks) {
|
|
216
|
+
return this
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
|
|
189
220
|
Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
|
|
190
221
|
Valid.assert(this.#hooks === null, "Hooks have already been configured.")
|
|
@@ -236,8 +267,14 @@ export default class ActionBuilder {
|
|
|
236
267
|
const newHooks = ActionHooks.new
|
|
237
268
|
|
|
238
269
|
const hooks = this.#hooks
|
|
239
|
-
if(hooks)
|
|
270
|
+
if(hooks) {
|
|
271
|
+
// If hooks is already an ActionHooks instance, use it directly
|
|
272
|
+
if(hooks instanceof ActionHooks)
|
|
273
|
+
return hooks
|
|
274
|
+
|
|
275
|
+
// Otherwise, wrap it in a new ActionHooks instance
|
|
240
276
|
return await newHooks({hooks}, this.#debug)
|
|
277
|
+
}
|
|
241
278
|
|
|
242
279
|
const hooksFile = this.#hooksFile
|
|
243
280
|
const hooksKind = this.#hooksKind
|
package/src/lib/ActionHooks.js
CHANGED
|
@@ -114,7 +114,7 @@ export default class ActionHooks {
|
|
|
114
114
|
static async new(config, debug) {
|
|
115
115
|
debug("Creating new HookManager instance with args: %o", 2, config)
|
|
116
116
|
|
|
117
|
-
const instance = new ActionHooks(config, debug)
|
|
117
|
+
const instance = new ActionHooks({...config, debug})
|
|
118
118
|
if(!instance.#hooks) {
|
|
119
119
|
const hooksFile = new FileObject(instance.#hooksFile)
|
|
120
120
|
|
|
@@ -151,7 +151,7 @@ export default class ActionHooks {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
return
|
|
154
|
+
return instance
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
@@ -170,8 +170,8 @@ export default class ActionHooks {
|
|
|
170
170
|
if(!hooks)
|
|
171
171
|
return
|
|
172
172
|
|
|
173
|
-
const stringActivityName = Data.isType("Symbol")
|
|
174
|
-
? activityName.description
|
|
173
|
+
const stringActivityName = Data.isType(activityName, "Symbol")
|
|
174
|
+
? activityName.description
|
|
175
175
|
: activityName
|
|
176
176
|
|
|
177
177
|
const hookName = this.#getActivityHookName(kind, stringActivityName)
|
package/src/lib/ActionRunner.js
CHANGED
|
@@ -112,7 +112,7 @@ export default class ActionRunner extends Piper {
|
|
|
112
112
|
if(await this.#hasPredicate(activity,predicate,context))
|
|
113
113
|
break
|
|
114
114
|
}
|
|
115
|
-
} else if(kindSplit
|
|
115
|
+
} else if(kindSplit) {
|
|
116
116
|
// SPLIT activity: parallel execution with splitter/rejoiner pattern
|
|
117
117
|
const splitter = activity.splitter
|
|
118
118
|
const rejoiner = activity.rejoiner
|
|
@@ -123,8 +123,19 @@ export default class ActionRunner extends Piper {
|
|
|
123
123
|
)
|
|
124
124
|
|
|
125
125
|
const original = context
|
|
126
|
-
const
|
|
127
|
-
|
|
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
|
+
|
|
128
139
|
const rejoined = rejoiner.call(activity.action, original,newContext)
|
|
129
140
|
|
|
130
141
|
context = rejoined
|
|
@@ -147,7 +158,7 @@ export default class ActionRunner extends Piper {
|
|
|
147
158
|
*
|
|
148
159
|
* When parallel=true, uses Piper.pipe() for concurrent execution with worker pool pattern.
|
|
149
160
|
* This is triggered by SPLIT activities where context is divided for parallel processing.
|
|
150
|
-
* Results from parallel execution are
|
|
161
|
+
* Results from parallel execution are returned directly as an array from Piper.pipe().
|
|
151
162
|
*
|
|
152
163
|
* @param {import("./Activity.js").default} activity Pipeline activity descriptor.
|
|
153
164
|
* @param {unknown} context Current pipeline context.
|
|
@@ -162,17 +173,15 @@ export default class ActionRunner extends Piper {
|
|
|
162
173
|
const opKind = activity.opKind
|
|
163
174
|
|
|
164
175
|
if(opKind === "ActionBuilder") {
|
|
165
|
-
if(activity.hooks
|
|
166
|
-
activity.op.
|
|
176
|
+
if(activity.hooks)
|
|
177
|
+
activity.op.withHooks(activity.hooks)
|
|
167
178
|
|
|
168
179
|
const runner = new this.constructor(activity.op, {
|
|
169
180
|
debug: this.#debug, name: activity.name
|
|
170
181
|
})
|
|
171
182
|
|
|
172
183
|
if(parallel) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return piped.filter(p => p.ok).map(p => p.value)
|
|
184
|
+
return await runner.pipe(context)
|
|
176
185
|
} else {
|
|
177
186
|
return await runner.run(context)
|
|
178
187
|
}
|
|
@@ -182,16 +191,14 @@ export default class ActionRunner extends Piper {
|
|
|
182
191
|
|
|
183
192
|
if(Data.isType(result, "ActionBuilder")) {
|
|
184
193
|
if(activity.hooks)
|
|
185
|
-
result.
|
|
194
|
+
result.withHooks(activity.hooks)
|
|
186
195
|
|
|
187
196
|
const runner = new this.constructor(result, {
|
|
188
197
|
debug: this.#debug, name: result.name
|
|
189
198
|
})
|
|
190
199
|
|
|
191
200
|
if(parallel) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return piped.filter(p => p.ok).map(p => p.value)
|
|
201
|
+
return await runner.pipe(context)
|
|
195
202
|
} else {
|
|
196
203
|
return await runner.run(context)
|
|
197
204
|
}
|
|
@@ -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
|
/**
|