@gesslar/actioneer 3.0.1 → 3.1.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/README.md +22 -628
- package/package.json +7 -7
- package/src/browser/lib/ActionBuilder.js +20 -4
- package/src/browser/lib/ActionRunner.js +29 -13
- package/src/browser/lib/ActionWrapper.js +9 -0
- package/src/browser/lib/Piper.js +2 -2
- package/src/index.js +9 -4
- package/src/lib/ActionHooks.js +4 -4
- package/src/types/browser/lib/ActionBuilder.d.ts +9 -0
- package/src/types/browser/lib/ActionBuilder.d.ts.map +1 -1
- package/src/types/browser/lib/ActionRunner.d.ts.map +1 -1
- package/src/types/browser/lib/ActionWrapper.d.ts +6 -0
- package/src/types/browser/lib/ActionWrapper.d.ts.map +1 -1
- package/src/types/index.d.ts +3 -2
- package/src/types/index.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Actioneer
|
|
2
2
|
|
|
3
|
-
Actioneer is a small, focused action orchestration library for Node.js and browser environments. It provides a fluent builder for composing activities and a concurrent runner with lifecycle hooks and control flow semantics (while/until/if/break/continue). The project is written as ES modules and targets Node
|
|
3
|
+
Actioneer is a small, focused action orchestration library for Node.js and browser environments. It provides a fluent builder for composing activities and a concurrent runner with lifecycle hooks and control flow semantics (while/until/if/break/continue). The project is written as ES modules and targets Node 24+ and modern browsers.
|
|
4
4
|
|
|
5
5
|
This repository extracts the action orchestration pieces from a larger codebase and exposes a compact API for building pipelines of work that can run concurrently with hook support and nested pipelines.
|
|
6
6
|
|
|
@@ -131,623 +131,17 @@ import { ActionBuilder, ActionRunner } from "@gesslar/actioneer"
|
|
|
131
131
|
|
|
132
132
|
If you'd like more complete typings or additional JSDoc, open an issue or send a PR — contributions welcome.
|
|
133
133
|
|
|
134
|
-
##
|
|
134
|
+
## Documentation
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
Full guides and API reference live at **[actioneer.gesslar.io](https://actioneer.gesslar.io)**. Highlights:
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
builder.do("processItem", ctx => {
|
|
146
|
-
ctx.result = ctx.input * 2
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### WHILE Mode
|
|
153
|
-
|
|
154
|
-
Loops while a predicate returns `true`. The predicate is evaluated **before** each iteration:
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
158
|
-
|
|
159
|
-
class CounterAction {
|
|
160
|
-
#shouldContinue = (ctx) => ctx.count < 10
|
|
161
|
-
|
|
162
|
-
#increment = (ctx) => {
|
|
163
|
-
ctx.count += 1
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
setup(builder) {
|
|
167
|
-
builder
|
|
168
|
-
.do("initialize", ctx => { ctx.count = 0 })
|
|
169
|
-
.do("countUp", ACTIVITY.WHILE, this.#shouldContinue, this.#increment)
|
|
170
|
-
.do("finish", ctx => { return ctx.count })
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
The activity will continue executing as long as the predicate returns `true`. Once it returns `false`, execution moves to the next activity.
|
|
176
|
-
|
|
177
|
-
### UNTIL Mode
|
|
178
|
-
|
|
179
|
-
Loops until a predicate returns `true`. The predicate is evaluated **after** each iteration:
|
|
180
|
-
|
|
181
|
-
```js
|
|
182
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
183
|
-
|
|
184
|
-
class ProcessorAction {
|
|
185
|
-
#queueIsEmpty = (ctx) => ctx.queue.length === 0
|
|
186
|
-
|
|
187
|
-
#processItem = (ctx) => {
|
|
188
|
-
const item = ctx.queue.shift()
|
|
189
|
-
ctx.processed.push(item)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
setup(builder) {
|
|
193
|
-
builder
|
|
194
|
-
.do("initialize", ctx => {
|
|
195
|
-
ctx.queue = [1, 2, 3, 4, 5]
|
|
196
|
-
ctx.processed = []
|
|
197
|
-
})
|
|
198
|
-
.do("process", ACTIVITY.UNTIL, this.#queueIsEmpty, this.#processItem)
|
|
199
|
-
.do("finish", ctx => { return ctx.processed })
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
The activity executes at least once, then continues while the predicate returns `false`. Once it returns `true`, execution moves to the next activity.
|
|
205
|
-
|
|
206
|
-
### IF Mode
|
|
207
|
-
|
|
208
|
-
Conditionally executes an activity based on a predicate. Unlike WHILE/UNTIL, IF executes at most once:
|
|
209
|
-
|
|
210
|
-
```js
|
|
211
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
212
|
-
|
|
213
|
-
class ConditionalAction {
|
|
214
|
-
#shouldProcess = (ctx) => ctx.value > 10
|
|
215
|
-
|
|
216
|
-
#processLargeValue = (ctx) => {
|
|
217
|
-
ctx.processed = ctx.value * 2
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
setup(builder) {
|
|
221
|
-
builder
|
|
222
|
-
.do("initialize", ctx => { ctx.value = 15 })
|
|
223
|
-
.do("maybeProcess", ACTIVITY.IF, this.#shouldProcess, this.#processLargeValue)
|
|
224
|
-
.do("finish", ctx => { return ctx })
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
If the predicate returns `true`, the activity executes once. If `false`, the activity is skipped entirely and execution moves to the next activity.
|
|
230
|
-
|
|
231
|
-
### BREAK Mode
|
|
232
|
-
|
|
233
|
-
Breaks out of a WHILE or UNTIL loop when a predicate returns `true`. BREAK must be used inside a nested ActionBuilder within a loop:
|
|
234
|
-
|
|
235
|
-
```js
|
|
236
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
237
|
-
|
|
238
|
-
class BreakExample {
|
|
239
|
-
setup(builder) {
|
|
240
|
-
builder
|
|
241
|
-
.do("initialize", ctx => {
|
|
242
|
-
ctx.count = 0
|
|
243
|
-
ctx.items = []
|
|
244
|
-
})
|
|
245
|
-
.do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100,
|
|
246
|
-
new ActionBuilder()
|
|
247
|
-
.do("increment", ctx => {
|
|
248
|
-
ctx.count++
|
|
249
|
-
ctx.items.push(ctx.count)
|
|
250
|
-
return ctx
|
|
251
|
-
})
|
|
252
|
-
.do("earlyExit", ACTIVITY.BREAK, ctx => ctx.count >= 5)
|
|
253
|
-
)
|
|
254
|
-
.do("finish", ctx => { return ctx.items }) // Returns [1, 2, 3, 4, 5]
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
When the BREAK predicate returns `true`, the loop terminates immediately and execution continues with the next activity after the loop.
|
|
260
|
-
|
|
261
|
-
**Important:** BREAK only works inside a nested ActionBuilder that is the operation of a WHILE or UNTIL activity. Using BREAK outside of a loop context will throw an error.
|
|
262
|
-
|
|
263
|
-
### CONTINUE Mode
|
|
264
|
-
|
|
265
|
-
Skips the remaining activities in the current loop iteration and continues to the next iteration. Like BREAK, CONTINUE must be used inside a nested ActionBuilder within a loop:
|
|
266
|
-
|
|
267
|
-
```js
|
|
268
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
269
|
-
|
|
270
|
-
class ContinueExample {
|
|
271
|
-
setup(builder) {
|
|
272
|
-
builder
|
|
273
|
-
.do("initialize", ctx => {
|
|
274
|
-
ctx.count = 0
|
|
275
|
-
ctx.processed = []
|
|
276
|
-
})
|
|
277
|
-
.do("loop", ACTIVITY.WHILE, ctx => ctx.count < 5,
|
|
278
|
-
new ActionBuilder()
|
|
279
|
-
.do("increment", ctx => {
|
|
280
|
-
ctx.count++
|
|
281
|
-
return ctx
|
|
282
|
-
})
|
|
283
|
-
.do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0)
|
|
284
|
-
.do("process", ctx => {
|
|
285
|
-
ctx.processed.push(ctx.count)
|
|
286
|
-
return ctx
|
|
287
|
-
})
|
|
288
|
-
)
|
|
289
|
-
.do("finish", ctx => { return ctx.processed }) // Returns [1, 3, 5]
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
When the CONTINUE predicate returns `true`, the remaining activities in that iteration are skipped, and the loop continues with its next iteration (re-evaluating the loop predicate for WHILE, or executing the operation then evaluating for UNTIL).
|
|
295
|
-
|
|
296
|
-
**Important:** Like BREAK, CONTINUE only works inside a nested ActionBuilder within a WHILE or UNTIL loop.
|
|
297
|
-
|
|
298
|
-
### Combining Control Flow
|
|
299
|
-
|
|
300
|
-
You can combine IF, BREAK, and CONTINUE within the same loop for complex control flow:
|
|
301
|
-
|
|
302
|
-
```js
|
|
303
|
-
class CombinedExample {
|
|
304
|
-
setup(builder) {
|
|
305
|
-
builder
|
|
306
|
-
.do("initialize", ctx => {
|
|
307
|
-
ctx.count = 0
|
|
308
|
-
ctx.results = []
|
|
309
|
-
})
|
|
310
|
-
.do("loop", ACTIVITY.WHILE, ctx => ctx.count < 100,
|
|
311
|
-
new ActionBuilder()
|
|
312
|
-
.do("increment", ctx => { ctx.count++; return ctx })
|
|
313
|
-
.do("exitAt10", ACTIVITY.BREAK, ctx => ctx.count > 10)
|
|
314
|
-
.do("skipEvens", ACTIVITY.CONTINUE, ctx => ctx.count % 2 === 0)
|
|
315
|
-
.do("processLarge", ACTIVITY.IF, ctx => ctx.count > 5, ctx => {
|
|
316
|
-
ctx.results.push(ctx.count * 10)
|
|
317
|
-
return ctx
|
|
318
|
-
})
|
|
319
|
-
.do("processAll", ctx => {
|
|
320
|
-
ctx.results.push(ctx.count)
|
|
321
|
-
return ctx
|
|
322
|
-
})
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
// Results: [1, 3, 5, 70, 7, 90, 9]
|
|
327
|
-
// - 1, 3, 5: odd numbers <= 5, just pushed
|
|
328
|
-
// - 7, 9: odd numbers > 5, pushed with *10 first, then pushed
|
|
329
|
-
// - evens skipped by CONTINUE
|
|
330
|
-
// - loop exits when count > 10
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### SPLIT Mode
|
|
334
|
-
|
|
335
|
-
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:
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
import { ActionBuilder, ACTIVITY } from "@gesslar/actioneer"
|
|
339
|
-
|
|
340
|
-
class ParallelProcessor {
|
|
341
|
-
#split = (ctx) => {
|
|
342
|
-
// Split context into multiple items for parallel processing
|
|
343
|
-
return ctx.items.map(item => ({ item, processedBy: "worker" }))
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
#rejoin = (originalCtx, splitResults) => {
|
|
347
|
-
// Recombine parallel results back into original context
|
|
348
|
-
originalCtx.results = splitResults.map(r => r.item)
|
|
349
|
-
return originalCtx
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
#processItem = (ctx) => {
|
|
353
|
-
ctx.item = ctx.item.toUpperCase()
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
setup(builder) {
|
|
357
|
-
builder
|
|
358
|
-
.do("initialize", ctx => {
|
|
359
|
-
ctx.items = ["apple", "banana", "cherry"]
|
|
360
|
-
})
|
|
361
|
-
.do("parallel", ACTIVITY.SPLIT, this.#split, this.#rejoin, this.#processItem)
|
|
362
|
-
.do("finish", ctx => { return ctx.results })
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**How SPLIT Mode Works:**
|
|
368
|
-
|
|
369
|
-
1. The **splitter** function receives the context and returns an array of contexts (one per parallel task)
|
|
370
|
-
2. Each split context is processed in parallel through the **operation** function
|
|
371
|
-
3. The **rejoiner** function receives the original context and the array of settled results from `Promise.allSettled()`
|
|
372
|
-
4. The rejoiner combines the results and returns the updated context
|
|
373
|
-
|
|
374
|
-
**Important: SPLIT uses `Promise.allSettled()`**
|
|
375
|
-
|
|
376
|
-
The SPLIT mode uses `Promise.allSettled()` internally to execute parallel operations. This means your **rejoiner** function will receive an array of settlement objects, not the raw context values. Each element in the array will be either:
|
|
377
|
-
|
|
378
|
-
- `{ status: "fulfilled", value: <result> }` for successful operations
|
|
379
|
-
- `{ status: "rejected", reason: <error> }` for failed operations
|
|
380
|
-
|
|
381
|
-
Your rejoiner must handle settled results accordingly. You can process them however you need - check each `status` manually, or use helper utilities like those in `@gesslar/toolkit`:
|
|
382
|
-
|
|
383
|
-
```js
|
|
384
|
-
import { Util } from "@gesslar/toolkit"
|
|
385
|
-
|
|
386
|
-
#rejoin = (originalCtx, settledResults) => {
|
|
387
|
-
// settledResults is an array of settlement objects
|
|
388
|
-
// Each has either { status: "fulfilled", value: ... }
|
|
389
|
-
// or { status: "rejected", reason: ... }
|
|
390
|
-
|
|
391
|
-
// Example: extract only successful results
|
|
392
|
-
originalCtx.results = Util.fulfilledValues(settledResults)
|
|
393
|
-
|
|
394
|
-
// Example: check for any failures
|
|
395
|
-
if (Util.anyRejected(settledResults)) {
|
|
396
|
-
originalCtx.errors = Util.rejectedReasons(
|
|
397
|
-
Util.settledAndRejected(settledResults)
|
|
398
|
-
)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return originalCtx
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Nested Pipelines with SPLIT:**
|
|
406
|
-
|
|
407
|
-
You can use nested ActionBuilders with SPLIT mode for complex parallel workflows:
|
|
408
|
-
|
|
409
|
-
```js
|
|
410
|
-
class NestedParallel {
|
|
411
|
-
#split = (ctx) => ctx.batches.map(batch => ({ batch }))
|
|
412
|
-
|
|
413
|
-
#rejoin = (original, results) => {
|
|
414
|
-
original.processed = results.flatMap(r => r.batch)
|
|
415
|
-
return original
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
setup(builder) {
|
|
419
|
-
builder
|
|
420
|
-
.do("parallel", ACTIVITY.SPLIT, this.#split, this.#rejoin,
|
|
421
|
-
new ActionBuilder(this)
|
|
422
|
-
.do("step1", ctx => { /* ... */ })
|
|
423
|
-
.do("step2", ctx => { /* ... */ })
|
|
424
|
-
)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Mode Constraints
|
|
430
|
-
|
|
431
|
-
- **Only one mode per activity**: Each activity can have only one mode. Attempting to combine modes will throw an error
|
|
432
|
-
- **SPLIT requires both functions**: The splitter and rejoiner are both mandatory for SPLIT mode
|
|
433
|
-
- **Predicates must return boolean**: All predicates (WHILE, UNTIL, IF, BREAK, CONTINUE) should return `true` or `false`
|
|
434
|
-
- **BREAK/CONTINUE require loop context**: These modes only work inside a nested ActionBuilder within a WHILE or UNTIL loop
|
|
435
|
-
|
|
436
|
-
### Mode Summary Table
|
|
437
|
-
|
|
438
|
-
| Mode | Signature | Predicate Timing | Use Case |
|
|
439
|
-
| ------------ | ---------------------------------------------------------- | ---------------- | ------------------------------------------- |
|
|
440
|
-
| **Default** | `.do(name, operation)` | N/A | Execute once per context |
|
|
441
|
-
| **WHILE** | `.do(name, ACTIVITY.WHILE, predicate, operation)` | Before iteration | Loop while condition is true |
|
|
442
|
-
| **UNTIL** | `.do(name, ACTIVITY.UNTIL, predicate, operation)` | After iteration | Loop until condition is true |
|
|
443
|
-
| **IF** | `.do(name, ACTIVITY.IF, predicate, operation)` | Before execution | Conditional execution (once or skip) |
|
|
444
|
-
| **BREAK** | `.do(name, ACTIVITY.BREAK, predicate)` | When reached | Exit enclosing WHILE/UNTIL loop |
|
|
445
|
-
| **CONTINUE** | `.do(name, ACTIVITY.CONTINUE, predicate)` | When reached | Skip to next iteration of enclosing loop |
|
|
446
|
-
| **SPLIT** | `.do(name, ACTIVITY.SPLIT, splitter, rejoiner, operation)` | N/A | Parallel execution with split/rejoin |
|
|
447
|
-
|
|
448
|
-
## Running Actions: `run()` vs `pipe()`
|
|
449
|
-
|
|
450
|
-
ActionRunner provides two methods for executing your action pipelines:
|
|
451
|
-
|
|
452
|
-
### `run(context)` - Single Context Execution
|
|
453
|
-
|
|
454
|
-
Executes the pipeline once with a single context. Returns the final context value directly, or throws if an error occurs.
|
|
455
|
-
|
|
456
|
-
```js
|
|
457
|
-
const builder = new ActionBuilder(new MyAction())
|
|
458
|
-
const runner = new ActionRunner(builder)
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
const result = await runner.run({input: "data"})
|
|
462
|
-
console.log(result) // Final context value
|
|
463
|
-
} catch (error) {
|
|
464
|
-
console.error("Pipeline failed:", error)
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
**Use `run()` when:**
|
|
469
|
-
|
|
470
|
-
- Processing a single context
|
|
471
|
-
- You want errors to throw immediately
|
|
472
|
-
- You prefer traditional try/catch error handling
|
|
473
|
-
|
|
474
|
-
### `pipe(contexts, maxConcurrent)` - Concurrent Batch Execution
|
|
475
|
-
|
|
476
|
-
Executes the pipeline concurrently across multiple contexts with a configurable concurrency limit. Returns an array of **settled results** - never throws on individual pipeline failures.
|
|
477
|
-
|
|
478
|
-
```js
|
|
479
|
-
const builder = new ActionBuilder(new MyAction())
|
|
480
|
-
const runner = new ActionRunner(builder)
|
|
481
|
-
|
|
482
|
-
const contexts = [{id: 1}, {id: 2}, {id: 3}]
|
|
483
|
-
const results = await runner.pipe(contexts, 4) // Max 4 concurrent
|
|
484
|
-
|
|
485
|
-
results.forEach((result, i) => {
|
|
486
|
-
if (result.status === "fulfilled") {
|
|
487
|
-
console.log(`Context ${i} succeeded:`, result.value)
|
|
488
|
-
} else {
|
|
489
|
-
console.error(`Context ${i} failed:`, result.reason)
|
|
490
|
-
}
|
|
491
|
-
})
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
**Use `pipe()` when:**
|
|
495
|
-
|
|
496
|
-
- Processing multiple contexts in parallel
|
|
497
|
-
- You want to control concurrency (default: 10)
|
|
498
|
-
- You need all results (both successes and failures)
|
|
499
|
-
- Error handling should be at the call site
|
|
500
|
-
|
|
501
|
-
**Important: `pipe()` returns settled results**
|
|
502
|
-
|
|
503
|
-
The `pipe()` method uses `Promise.allSettled()` internally and returns an array of settlement objects:
|
|
504
|
-
|
|
505
|
-
- `{status: "fulfilled", value: <result>}` for successful executions
|
|
506
|
-
- `{status: "rejected", reason: <error>}` for failed executions
|
|
507
|
-
|
|
508
|
-
This design ensures error handling responsibility stays at the call site - you decide how to handle failures rather than the framework deciding for you.
|
|
509
|
-
|
|
510
|
-
## Pipeline Completion: `done()`
|
|
511
|
-
|
|
512
|
-
The `done()` method registers a callback that executes after all activities in the pipeline complete, regardless of whether an error occurred. This is useful for cleanup, finalization, or returning a transformed result.
|
|
513
|
-
|
|
514
|
-
```js
|
|
515
|
-
import { ActionBuilder, ActionRunner } from "@gesslar/actioneer"
|
|
516
|
-
|
|
517
|
-
class MyAction {
|
|
518
|
-
setup(builder) {
|
|
519
|
-
builder
|
|
520
|
-
.do("step1", ctx => { ctx.a = 1 })
|
|
521
|
-
.do("step2", ctx => { ctx.b = 2 })
|
|
522
|
-
.done(ctx => {
|
|
523
|
-
// This runs after all activities complete
|
|
524
|
-
return { total: ctx.a + ctx.b }
|
|
525
|
-
})
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const builder = new ActionBuilder(new MyAction())
|
|
530
|
-
const runner = new ActionRunner(builder)
|
|
531
|
-
const result = await runner.run({})
|
|
532
|
-
console.log(result) // { total: 3 }
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### Key Behaviors
|
|
536
|
-
|
|
537
|
-
- **Always executes**: The `done()` callback runs even if an earlier activity throws an error (similar to `finally` in try/catch)
|
|
538
|
-
- **Top-level only**: The callback only runs for the outermost pipeline, not for nested builders inside loops (WHILE/UNTIL). However, for SPLIT activities, `done()` runs for each split context since each is an independent execution
|
|
539
|
-
- **Transform the result**: Whatever you return from `done()` becomes the final pipeline result
|
|
540
|
-
- **Access to action context**: The callback is bound to the action instance, so `this` refers to your action class
|
|
541
|
-
- **Async support**: The callback can be async and return a Promise
|
|
542
|
-
|
|
543
|
-
### Use Cases
|
|
544
|
-
|
|
545
|
-
**Cleanup resources:**
|
|
546
|
-
|
|
547
|
-
```js
|
|
548
|
-
builder
|
|
549
|
-
.do("openConnection", ctx => { ctx.conn = openDb() })
|
|
550
|
-
.do("query", ctx => { ctx.data = ctx.conn.query("SELECT *") })
|
|
551
|
-
.done(ctx => {
|
|
552
|
-
ctx.conn.close() // Always close, even on error
|
|
553
|
-
return ctx.data
|
|
554
|
-
})
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
**Transform the final result:**
|
|
558
|
-
|
|
559
|
-
```js
|
|
560
|
-
builder
|
|
561
|
-
.do("gather", ctx => { ctx.items = [1, 2, 3] })
|
|
562
|
-
.do("process", ctx => { ctx.items = ctx.items.map(x => x * 2) })
|
|
563
|
-
.done(ctx => ctx.items) // Return just the items array, not the whole context
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
**Logging and metrics:**
|
|
567
|
-
|
|
568
|
-
```js
|
|
569
|
-
builder
|
|
570
|
-
.do("start", ctx => { ctx.startTime = Date.now() })
|
|
571
|
-
.do("work", ctx => { /* ... */ })
|
|
572
|
-
.done(ctx => {
|
|
573
|
-
console.log(`Pipeline completed in ${Date.now() - ctx.startTime}ms`)
|
|
574
|
-
return ctx
|
|
575
|
-
})
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
## ActionHooks
|
|
579
|
-
|
|
580
|
-
Actioneer supports lifecycle hooks that can execute before and after each activity in your pipeline. Hooks can be configured by file path (Node.js only) or by providing a pre-instantiated hooks object (Node.js and browser).
|
|
581
|
-
|
|
582
|
-
### Hook System Overview
|
|
583
|
-
|
|
584
|
-
The hook system allows you to:
|
|
585
|
-
|
|
586
|
-
- Execute code before and after each activity in your pipeline
|
|
587
|
-
- Implement setup and cleanup logic
|
|
588
|
-
- Add observability and logging to your pipelines
|
|
589
|
-
- Modify or inspect the context flowing through activities
|
|
590
|
-
|
|
591
|
-
### Configuring Hooks
|
|
592
|
-
|
|
593
|
-
#### Browser: Pre-instantiated Hooks
|
|
594
|
-
|
|
595
|
-
In browser environments, you must provide pre-instantiated hooks objects:
|
|
596
|
-
|
|
597
|
-
```js
|
|
598
|
-
import {ActionBuilder, ActionRunner} from "@gesslar/actioneer"
|
|
599
|
-
|
|
600
|
-
class MyActionHooks {
|
|
601
|
-
constructor({debug}) {
|
|
602
|
-
this.debug = debug
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
async before$prepare(context) {
|
|
606
|
-
this.debug("About to prepare", context)
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
async after$prepare(context) {
|
|
610
|
-
this.debug("Finished preparing", context)
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const hooks = new MyActionHooks({debug: console.log})
|
|
615
|
-
|
|
616
|
-
class MyAction {
|
|
617
|
-
setup(builder) {
|
|
618
|
-
builder
|
|
619
|
-
.withHooks(hooks)
|
|
620
|
-
.do("prepare", ctx => { ctx.count = 0 })
|
|
621
|
-
.do("work", ctx => { ctx.count += 1 })
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const builder = new ActionBuilder(new MyAction())
|
|
626
|
-
const runner = new ActionRunner(builder)
|
|
627
|
-
const result = await runner.pipe([{}], 4)
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
#### Node.js: File-based or Pre-instantiated
|
|
631
|
-
|
|
632
|
-
**Option 1: Load hooks from a file** (Node.js only)
|
|
633
|
-
|
|
634
|
-
```js
|
|
635
|
-
import {ActionBuilder, ActionRunner} from "@gesslar/actioneer"
|
|
636
|
-
|
|
637
|
-
class MyAction {
|
|
638
|
-
setup(builder) {
|
|
639
|
-
builder
|
|
640
|
-
.withHooksFile("./hooks/MyActionHooks.js", "MyActionHooks")
|
|
641
|
-
.do("prepare", ctx => { ctx.count = 0 })
|
|
642
|
-
.do("work", ctx => { ctx.count += 1 })
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const builder = new ActionBuilder(new MyAction())
|
|
647
|
-
const runner = new ActionRunner(builder)
|
|
648
|
-
const result = await runner.pipe([{}], 4)
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
**Option 2: Provide a pre-instantiated hooks object** (Node.js and browser)
|
|
652
|
-
|
|
653
|
-
```js
|
|
654
|
-
import {ActionBuilder, ActionRunner} from "@gesslar/actioneer"
|
|
655
|
-
import {MyActionHooks} from "./hooks/MyActionHooks.js"
|
|
656
|
-
|
|
657
|
-
const hooks = new MyActionHooks({debug: console.log})
|
|
658
|
-
|
|
659
|
-
class MyAction {
|
|
660
|
-
setup(builder) {
|
|
661
|
-
builder
|
|
662
|
-
.withHooks(hooks)
|
|
663
|
-
.do("prepare", ctx => { ctx.count = 0 })
|
|
664
|
-
.do("work", ctx => { ctx.count += 1 })
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
const builder = new ActionBuilder(new MyAction())
|
|
669
|
-
const runner = new ActionRunner(builder)
|
|
670
|
-
const result = await runner.pipe([{}], 4)
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### Writing Hooks
|
|
674
|
-
|
|
675
|
-
Hooks are classes exported from a module. The hook methods follow a naming convention: `event$activityName`.
|
|
676
|
-
|
|
677
|
-
```js
|
|
678
|
-
// hooks/MyActionHooks.js
|
|
679
|
-
export class MyActionHooks {
|
|
680
|
-
constructor({ debug }) {
|
|
681
|
-
this.debug = debug
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Hook that runs before the "prepare" activity
|
|
685
|
-
async before$prepare(context) {
|
|
686
|
-
this.debug("About to prepare", context)
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// Hook that runs after the "prepare" activity
|
|
690
|
-
async after$prepare(context) {
|
|
691
|
-
this.debug("Finished preparing", context)
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Hook that runs before the "work" activity
|
|
695
|
-
async before$work(context) {
|
|
696
|
-
this.debug("Starting work", context)
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Hook that runs after the "work" activity
|
|
700
|
-
async after$work(context) {
|
|
701
|
-
this.debug("Work complete", context)
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Optional: setup hook runs once at initialization
|
|
705
|
-
async setup(args) {
|
|
706
|
-
this.debug("Hooks initialized")
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Optional: cleanup hook for teardown
|
|
710
|
-
async cleanup(args) {
|
|
711
|
-
this.debug("Hooks cleaned up")
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
### Hook Naming Convention
|
|
717
|
-
|
|
718
|
-
Activity names are transformed to hook method names:
|
|
719
|
-
|
|
720
|
-
- Spaces are removed and words are camelCased: `"do work"` → `before$doWork` / `after$doWork`
|
|
721
|
-
- Non-word characters are stripped: `"step-1"` → `before$step1` / `after$step1`
|
|
722
|
-
- First word stays lowercase: `"Prepare Data"` → `before$prepareData` / `after$prepareData`
|
|
723
|
-
|
|
724
|
-
### Hook Timeout
|
|
725
|
-
|
|
726
|
-
By default, hooks have a 1-second (1000ms) timeout. If a hook exceeds this timeout, the pipeline will throw a `Sass` error. You can configure the timeout when creating the hooks:
|
|
727
|
-
|
|
728
|
-
```js
|
|
729
|
-
new ActionHooks({
|
|
730
|
-
actionKind: "MyActionHooks",
|
|
731
|
-
hooksFile: "./hooks.js",
|
|
732
|
-
hookTimeout: 5000, // 5 seconds
|
|
733
|
-
debug: console.log
|
|
734
|
-
})
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Nested Pipelines and Hooks
|
|
738
|
-
|
|
739
|
-
When you nest ActionBuilders (for branching or parallel execution), the parent's hooks are automatically passed to all children, ensuring consistent hook behavior throughout the entire pipeline hierarchy.
|
|
740
|
-
|
|
741
|
-
### Optional TypeScript (local, opt-in)
|
|
742
|
-
|
|
743
|
-
This project intentionally avoids committing TypeScript tool configuration. If you'd like to use TypeScript's checker locally (for editor integration or optional JSDoc checking), you can drop a `tsconfig.json` in your working copy — `tsconfig.json` is already in the repository `.gitignore`, so feel free to typecheck yourselves into oblivion.
|
|
744
|
-
|
|
745
|
-
Two common local options:
|
|
746
|
-
|
|
747
|
-
- Editor/resolve-only (no checking): set `moduleResolution`/`module` and `noEmit` so the editor resolves imports consistently without typechecking.
|
|
748
|
-
- Local JSDoc checks: set `allowJs: true` and `checkJs: true` with `noEmit: true` and `strict: false` to let the TypeScript checker validate JSDoc without enforcing strict typing.
|
|
749
|
-
|
|
750
|
-
Examples of minimal configs and one-liners to run them are in the project discussion; use them locally if you want an optional safety net. The repository will not require or enforce these files.
|
|
138
|
+
- [Activity Modes](https://actioneer.gesslar.io/guides/activity-modes/) — the six execution modes (`WHILE`, `UNTIL`, `IF`, `BREAK`, `CONTINUE`, `SPLIT`)
|
|
139
|
+
- [Control Flow](https://actioneer.gesslar.io/guides/control-flow/) — `BREAK` and `CONTINUE` inside loops
|
|
140
|
+
- [Parallelism with SPLIT](https://actioneer.gesslar.io/guides/split/) — split/rejoin and settled results
|
|
141
|
+
- [run() vs pipe()](https://actioneer.gesslar.io/guides/run-vs-pipe/) — single vs concurrent execution
|
|
142
|
+
- [Finalizing with done()](https://actioneer.gesslar.io/guides/done/) — cleanup and result shaping
|
|
143
|
+
- [Lifecycle Hooks](https://actioneer.gesslar.io/guides/hooks/) — `before$` / `after$` hooks
|
|
144
|
+
- [API Reference](https://actioneer.gesslar.io/reference/action-builder/) — `ActionBuilder`, `ActionRunner`, `Activity`, `ActionHooks`, `Piper`
|
|
751
145
|
|
|
752
146
|
## Testing
|
|
753
147
|
|
|
@@ -770,7 +164,7 @@ Tests are organized in `tests/unit/` with one file per class. All tests use Node
|
|
|
770
164
|
|
|
771
165
|
## Publishing
|
|
772
166
|
|
|
773
|
-
This repository is prepared for npm publishing. The package uses ESM and targets Node
|
|
167
|
+
This repository is prepared for npm publishing. The package uses ESM and targets Node 24+. The `files` field includes the `src/` folder and types. If you publish, ensure the `version` in `package.json` is updated and you have an npm token configured on the CI runner.
|
|
774
168
|
|
|
775
169
|
A simple publish checklist:
|
|
776
170
|
|
|
@@ -784,17 +178,6 @@ A simple publish checklist:
|
|
|
784
178
|
|
|
785
179
|
Contributions and issues are welcome. Please open issues for feature requests or bugs. If you're submitting a PR, include tests for new behavior where possible.
|
|
786
180
|
|
|
787
|
-
## License
|
|
788
|
-
|
|
789
|
-
`@gesslar/actioneer` is released into the public domain under the [Unlicense](LICENSE.txt).
|
|
790
|
-
|
|
791
|
-
This package includes or depends on third-party components under their own
|
|
792
|
-
licenses:
|
|
793
|
-
|
|
794
|
-
| Dependency | License |
|
|
795
|
-
| --- | --- |
|
|
796
|
-
| [@gesslar/toolkit](https://github.com/gesslar/toolkit) | 0BSD |
|
|
797
|
-
|
|
798
181
|
## Most Portum
|
|
799
182
|
|
|
800
183
|
As this is my repo, I have some opinions I would like to express and be made clear.
|
|
@@ -804,3 +187,14 @@ As this is my repo, I have some opinions I would like to express and be made cle
|
|
|
804
187
|
- Thank you, I love you. BYEBYE!
|
|
805
188
|
|
|
806
189
|
🤗
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
`@gesslar/actioneer` is released under the [0BSD](LICENSE.txt).
|
|
194
|
+
|
|
195
|
+
This package includes or depends on third-party components under their own
|
|
196
|
+
licenses:
|
|
197
|
+
|
|
198
|
+
| Dependency | License |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| [@gesslar/toolkit](https://github.com/gesslar/toolkit) | 0BSD |
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "gesslar",
|
|
6
6
|
"url": "https://gesslar.dev"
|
|
7
7
|
},
|
|
8
|
-
"version": "3.0
|
|
8
|
+
"version": "3.1.0",
|
|
9
9
|
"license": "0BSD",
|
|
10
10
|
"homepage": "https://github.com/gesslar/toolkit#readme",
|
|
11
11
|
"repository": {
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"src/",
|
|
44
|
-
"
|
|
44
|
+
"LICENSE.txt"
|
|
45
45
|
],
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"engines": {
|
|
48
|
-
"node": ">=24
|
|
48
|
+
"node": ">=24"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"lint": "eslint src/",
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
"major": "npm version major"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@gesslar/toolkit": "^5.
|
|
65
|
+
"@gesslar/toolkit": "^5.5.2"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@gesslar/uglier": "^2.4.
|
|
69
|
-
"eslint": "^10.
|
|
70
|
-
"typescript": "^6.0.
|
|
68
|
+
"@gesslar/uglier": "^2.4.1",
|
|
69
|
+
"eslint": "^10.4.1",
|
|
70
|
+
"typescript": "^6.0.3"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -51,6 +51,16 @@ import {ACTIVITY} from "./Activity.js"
|
|
|
51
51
|
* @class ActionBuilder
|
|
52
52
|
*/
|
|
53
53
|
export default class ActionBuilder {
|
|
54
|
+
/**
|
|
55
|
+
* The ActionHooks class used to resolve hooks. Defaults to the
|
|
56
|
+
* browser-compatible implementation (pre-instantiated hooks only). The Node
|
|
57
|
+
* entry point overrides this with the file-loading subclass so that
|
|
58
|
+
* {@link ActionBuilder#withHooksFile} works.
|
|
59
|
+
*
|
|
60
|
+
* @type {typeof ActionHooks}
|
|
61
|
+
*/
|
|
62
|
+
static HooksClass = ActionHooks
|
|
63
|
+
|
|
54
64
|
/** @type {ActionBuilderAction?} */
|
|
55
65
|
#action = null
|
|
56
66
|
/** @type {Map<string|symbol, ActivityDefinition>} */
|
|
@@ -334,23 +344,29 @@ export default class ActionBuilder {
|
|
|
334
344
|
}
|
|
335
345
|
|
|
336
346
|
async #getHooks() {
|
|
337
|
-
const
|
|
347
|
+
const HooksClass = ActionBuilder.HooksClass
|
|
338
348
|
|
|
339
349
|
const hooks = this.#hooks
|
|
340
350
|
if(hooks) {
|
|
341
|
-
// If hooks is already an ActionHooks instance, use it directly
|
|
351
|
+
// If hooks is already an ActionHooks instance, use it directly.
|
|
352
|
+
// The base class catches subclass instances too.
|
|
342
353
|
if(hooks instanceof ActionHooks)
|
|
343
354
|
return hooks
|
|
344
355
|
|
|
345
356
|
// Otherwise, wrap it in a new ActionHooks instance
|
|
346
|
-
return await
|
|
357
|
+
return await HooksClass.new({hooks}, this.#debug)
|
|
347
358
|
}
|
|
348
359
|
|
|
349
360
|
const hooksFile = this.#hooksFile
|
|
350
361
|
const hooksKind = this.#hooksKind
|
|
351
362
|
|
|
363
|
+
// File loading is only available on the Node HooksClass; the loader keys the
|
|
364
|
+
// class to instantiate off `actionKind`.
|
|
352
365
|
if(hooksFile && hooksKind)
|
|
353
|
-
return await
|
|
366
|
+
return await HooksClass.new(
|
|
367
|
+
{hooksFile, actionKind: hooksKind},
|
|
368
|
+
this.#debug,
|
|
369
|
+
)
|
|
354
370
|
}
|
|
355
371
|
|
|
356
372
|
/**
|
|
@@ -63,7 +63,25 @@ export default class ActionRunner extends Piper {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Builds the ActionWrapper on first use and caches it for subsequent calls.
|
|
67
|
+
*
|
|
68
|
+
* Hooks are configured by the action's `setup()`, which only runs during
|
|
69
|
+
* `build()`. The setup/cleanup lifecycle hooks fire from {@link Piper#pipe}
|
|
70
|
+
* before any item is processed, so the wrapper must be built here too —
|
|
71
|
+
* otherwise the resolved hooks would not yet exist when setup runs.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Promise<import("./ActionWrapper.js").default>} The built wrapper.
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
async #ensureBuilt() {
|
|
77
|
+
if(!this.#actionWrapper)
|
|
78
|
+
this.#actionWrapper = await this.#actionBuilder.build(this)
|
|
79
|
+
|
|
80
|
+
return this.#actionWrapper
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Invokes the `setup` lifecycle hook on the resolved hooks object, if defined.
|
|
67
85
|
* Registered as a Piper setup step so it fires before any items are processed.
|
|
68
86
|
*
|
|
69
87
|
* @param {unknown} ctx - Value passed by {@link Piper#pipe} (the items array).
|
|
@@ -71,29 +89,30 @@ export default class ActionRunner extends Piper {
|
|
|
71
89
|
* @private
|
|
72
90
|
*/
|
|
73
91
|
async #setupHooks(ctx) {
|
|
74
|
-
const
|
|
75
|
-
const ah =
|
|
92
|
+
const wrapper = await this.#ensureBuilt()
|
|
93
|
+
const ah = wrapper.hooks
|
|
76
94
|
const setup = ah?.setup
|
|
77
95
|
|
|
78
96
|
if(setup)
|
|
79
|
-
await setup.call(ah, ctx)
|
|
97
|
+
await setup.call(ah.hooks, ctx)
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
/**
|
|
83
|
-
* Invokes the `cleanup` lifecycle hook on the
|
|
84
|
-
* Registered as a Piper teardown step so it fires after all items
|
|
101
|
+
* Invokes the `cleanup` lifecycle hook on the resolved hooks object, if
|
|
102
|
+
* defined. Registered as a Piper teardown step so it fires after all items
|
|
103
|
+
* are processed.
|
|
85
104
|
*
|
|
86
105
|
* @param {unknown} ctx - Value passed by {@link Piper#pipe} (the items array).
|
|
87
106
|
* @returns {Promise<void>}
|
|
88
107
|
* @private
|
|
89
108
|
*/
|
|
90
109
|
async #cleanupHooks(ctx) {
|
|
91
|
-
const
|
|
92
|
-
const ah =
|
|
110
|
+
const wrapper = await this.#ensureBuilt()
|
|
111
|
+
const ah = wrapper.hooks
|
|
93
112
|
const cleanup = ah?.cleanup
|
|
94
113
|
|
|
95
114
|
if(cleanup)
|
|
96
|
-
await cleanup.call(ah, ctx)
|
|
115
|
+
await cleanup.call(ah.hooks, ctx)
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
/**
|
|
@@ -108,10 +127,7 @@ export default class ActionRunner extends Piper {
|
|
|
108
127
|
* @throws {Tantrum} When both an activity and the done callback fail.
|
|
109
128
|
*/
|
|
110
129
|
async run(context, parentWrapper=null) {
|
|
111
|
-
|
|
112
|
-
this.#actionWrapper = await this.#actionBuilder.build(this)
|
|
113
|
-
|
|
114
|
-
const actionWrapper = this.#actionWrapper
|
|
130
|
+
const actionWrapper = await this.#ensureBuilt()
|
|
115
131
|
const activities = Array.from(actionWrapper.activities)
|
|
116
132
|
|
|
117
133
|
let caughtError = null
|
|
@@ -113,4 +113,13 @@ export default class ActionWrapper {
|
|
|
113
113
|
get action() {
|
|
114
114
|
return this.#action
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the resolved hooks manager.
|
|
119
|
+
*
|
|
120
|
+
* @returns {import("./ActionHooks.js").default?} Hooks manager or null.
|
|
121
|
+
*/
|
|
122
|
+
get hooks() {
|
|
123
|
+
return this.#hooks
|
|
124
|
+
}
|
|
116
125
|
}
|
package/src/browser/lib/Piper.js
CHANGED
|
@@ -170,9 +170,9 @@ export default class Piper extends NotifyClass {
|
|
|
170
170
|
* @param {Array<unknown>} settled - Results from settleAll
|
|
171
171
|
* @throws {Tantrum} - If any settled result was rejected
|
|
172
172
|
*/
|
|
173
|
-
#processResult(
|
|
173
|
+
#processResult(message, settled) {
|
|
174
174
|
if(Promised.hasRejected(settled))
|
|
175
|
-
Promised.throw(settled)
|
|
175
|
+
Promised.throw(message, settled)
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/**
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// Browser-compatible base classes
|
|
2
|
-
|
|
2
|
+
import ActionBuilder from "./browser/lib/ActionBuilder.js"
|
|
3
|
+
import ActionHooks from "./lib/ActionHooks.js"
|
|
4
|
+
|
|
5
|
+
// Node-enhanced ActionHooks (extends browser, adds FileObject support). Wiring
|
|
6
|
+
// it into the builder makes withHooksFile() load hooks from disk under the Node
|
|
7
|
+
// entry point, while the browser entry keeps the pre-instantiated-only default.
|
|
8
|
+
ActionBuilder.HooksClass = ActionHooks
|
|
9
|
+
|
|
10
|
+
export {ActionBuilder, ActionHooks}
|
|
3
11
|
export {default as ActionRunner} from "./browser/lib/ActionRunner.js"
|
|
4
12
|
export {default as ActionWrapper} from "./browser/lib/ActionWrapper.js"
|
|
5
13
|
export {default as Activity, ACTIVITY} from "./browser/lib/Activity.js"
|
|
6
14
|
export {default as Piper} from "./browser/lib/Piper.js"
|
|
7
|
-
|
|
8
|
-
// Node-enhanced version (extends browser, adds FileObject support)
|
|
9
|
-
export {default as ActionHooks} from "./lib/ActionHooks.js"
|
package/src/lib/ActionHooks.js
CHANGED
|
@@ -75,11 +75,11 @@ export default class ActionHooks extends BrowserActionHooks {
|
|
|
75
75
|
|
|
76
76
|
const hooksFile = new FileObject(config.hooksFile)
|
|
77
77
|
|
|
78
|
-
debug("Loading hooks from %o", 2, hooksFile.
|
|
79
|
-
debug("Checking hooks file exists: %o", 2, hooksFile.
|
|
78
|
+
debug("Loading hooks from %o", 2, hooksFile.path)
|
|
79
|
+
debug("Checking hooks file exists: %o", 2, hooksFile.path)
|
|
80
80
|
|
|
81
81
|
if(!await hooksFile.exists)
|
|
82
|
-
throw Sass.new(`No such hooks file, ${hooksFile.
|
|
82
|
+
throw Sass.new(`No such hooks file, ${hooksFile.path}`)
|
|
83
83
|
|
|
84
84
|
try {
|
|
85
85
|
const hooksImport = await hooksFile.import()
|
|
@@ -100,7 +100,7 @@ export default class ActionHooks extends BrowserActionHooks {
|
|
|
100
100
|
// Create instance with loaded hooks
|
|
101
101
|
return new ActionHooks({...config, hooks, debug})
|
|
102
102
|
} catch(error) {
|
|
103
|
-
debug("Failed to load hooks %o: %o", 1, hooksFile.
|
|
103
|
+
debug("Failed to load hooks %o: %o", 1, hooksFile.path, error.message)
|
|
104
104
|
|
|
105
105
|
return null
|
|
106
106
|
}
|
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
* @class ActionBuilder
|
|
45
45
|
*/
|
|
46
46
|
export default class ActionBuilder {
|
|
47
|
+
/**
|
|
48
|
+
* The ActionHooks class used to resolve hooks. Defaults to the
|
|
49
|
+
* browser-compatible implementation (pre-instantiated hooks only). The Node
|
|
50
|
+
* entry point overrides this with the file-loading subclass so that
|
|
51
|
+
* {@link ActionBuilder#withHooksFile} works.
|
|
52
|
+
*
|
|
53
|
+
* @type {typeof ActionHooks}
|
|
54
|
+
*/
|
|
55
|
+
static HooksClass: typeof ActionHooks;
|
|
47
56
|
/**
|
|
48
57
|
* Creates a new ActionBuilder instance with the provided action callback.
|
|
49
58
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionBuilder.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;
|
|
1
|
+
{"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionBuilder.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IACE;;;;;;;OAOG;IACH,mBAFU,OAAO,WAAW,CAEG;IAmB/B;;;;;OAKG;IACH,qBAHW,mBAAmB,mBACnB,mBAAmB,EAkB7B;IAED,yBAEC;;;;;;;;;;;;;;;IAWE,SACQ,MAAM,GAAC,MAAM,MACb,cAAc,GACZ,aAAa,CACzB;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,QACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAC5C,aAAa,CACzB;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,QACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,MAC9C,cAAc,GAAC,aAAa,GAC1B,aAAa,CACzB;;;;;;;;;;IAGE,SACQ,MAAM,GAAC,MAAM,QACb,MAAM,YACN,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,YAC7B,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,MAC5D,cAAc,GAAC,aAAa,GAC1B,aAAa,CACzB;IAkED;;;;;;;OAOG;IACH,yBALW,MAAM,aACN,MAAM,GACJ,aAAa,CAYzB;IAED;;;;;;OAMG;IACH,iBAJW,WAAW,GACT,aAAa,CAgBzB;IAED;;;;;;OAMG;IACH,mBAHW,mBAAmB,GACjB,aAAa,CAczB;IAED;;;;;OAKG;IACH,eAHW,cAAc,GACZ,aAAa,CAOzB;IAeD;;;;;;OAMG;IACH,cAHW,YAAY,GACV,OAAO,CAAC,aAAa,CAAC,CAoClC;IA4BD;;;;;OAKG;IACH,aAFa,MAAM,cAAU,IAAI,CAIhC;;CACF;;;;sBAjXY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;;;;WAGjE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;YAQhC,mBAAmB,GAAC,IAAI;;;;WACxB,OAAO,GAAC,IAAI;;;;UACZ,MAAM,GAAC,MAAM;;;;QACb,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO;;;;;;;;sBAEzC,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;;;;6BAE/C,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;wBA1BnC,kBAAkB;6CAMA,mBAAmB;0BAPnC,oBAAoB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionRunner.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH;;;;;GAKG;AAEH;;;;;;GAMG;AACH;IAaE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAoB7B;
|
|
1
|
+
{"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionRunner.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH;;;;;GAKG;AAEH;;;;;;GAMG;AACH;IAaE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAoB7B;IAuDD;;;;;;;;;;OAUG;IACH,aANW,OAAO,kBACP,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,GACvC,OAAO,CAAC,OAAO,CAAC,CAiJ5B;;CAiGF;sBAlWY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;;;kBAT7D,YAAY"}
|
|
@@ -58,6 +58,12 @@ export default class ActionWrapper {
|
|
|
58
58
|
* @returns {unknown|null} Action instance or null.
|
|
59
59
|
*/
|
|
60
60
|
get action(): unknown | null;
|
|
61
|
+
/**
|
|
62
|
+
* Get the resolved hooks manager.
|
|
63
|
+
*
|
|
64
|
+
* @returns {import("./ActionHooks.js").default?} Hooks manager or null.
|
|
65
|
+
*/
|
|
66
|
+
get hooks(): import("./ActionHooks.js").default | null;
|
|
61
67
|
#private;
|
|
62
68
|
}
|
|
63
69
|
export type WrappedActivityConfig = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionWrapper.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionWrapper.js"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH;;GAEG;AACH;IAwBE;;;;;;;;;OASG;IACH,sEANG;QAAwD,UAAU,EAA1D,GAAG,CAAC,MAAM,GAAC,MAAM,EAAE,qBAAqB,CAAC;QACgC,KAAK,EAA9E,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;QACxB,KAAK,EAA/C,OAAO,kBAAkB,EAAE,OAAO,OAAC;QAC0B,IAAI,cAAtD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;QAChC,MAAM,GAArB,OAAO;KACjB,EAuBA;IAED;;;;;OAKG;IACH,UAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,kBAFa,QAAQ,CAAC,QAAQ,CAAC,CAI9B;IAED;;;;OAIG;IACH,YAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAC,IAAI,CAIjE;IAED;;;;OAIG;IACH,cAFa,OAAO,GAAC,IAAI,CAIxB;;CACF;;;;;
|
|
1
|
+
{"version":3,"file":"ActionWrapper.d.ts","sourceRoot":"","sources":["../../../browser/lib/ActionWrapper.js"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH;;GAEG;AACH;IAwBE;;;;;;;;;OASG;IACH,sEANG;QAAwD,UAAU,EAA1D,GAAG,CAAC,MAAM,GAAC,MAAM,EAAE,qBAAqB,CAAC;QACgC,KAAK,EAA9E,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;QACxB,KAAK,EAA/C,OAAO,kBAAkB,EAAE,OAAO,OAAC;QAC0B,IAAI,cAAtD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;QAChC,MAAM,GAArB,OAAO;KACjB,EAuBA;IAED;;;;;OAKG;IACH,UAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,kBAFa,QAAQ,CAAC,QAAQ,CAAC,CAI9B;IAED;;;;OAIG;IACH,YAFa,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAC,IAAI,CAIjE;IAED;;;;OAIG;IACH,cAFa,OAAO,GAAC,IAAI,CAIxB;IAED;;;;OAIG;IACH,aAFa,OAAO,kBAAkB,EAAE,OAAO,OAAC,CAI/C;;CACF;;;;;UAlHa,MAAM,GAAC,MAAM;;;;QACb,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC,GAAC,aAAa;;;;;;;;sBAElD,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC;;;;aAC9C,OAAO;;;;uBACG,MAAM,UAAU,MAAM,WAAW,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;qBAf3D,eAAe"}
|
package/src/types/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { default as ActionBuilder } from "./browser/lib/ActionBuilder.js";
|
|
2
1
|
export { default as ActionRunner } from "./browser/lib/ActionRunner.js";
|
|
3
2
|
export { default as ActionWrapper } from "./browser/lib/ActionWrapper.js";
|
|
4
3
|
export { default as Piper } from "./browser/lib/Piper.js";
|
|
5
|
-
|
|
4
|
+
import ActionBuilder from "./browser/lib/ActionBuilder.js";
|
|
5
|
+
import ActionHooks from "./lib/ActionHooks.js";
|
|
6
|
+
export { ActionBuilder, ActionHooks };
|
|
6
7
|
export { default as Activity, ACTIVITY } from "./browser/lib/Activity.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/src/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":";;;0BAC0B,gCAAgC;wBAClC,sBAAsB"}
|