@gesslar/actioneer 0.2.0 → 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.
@@ -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 false (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
- #kind = null
34
+ /** @type {((context: unknown) => boolean|Promise<boolean>)|null} */
22
35
  #pred = null
23
- #hooks = null
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 {{action: unknown, name: string, op: (context: unknown) => unknown|Promise<unknown>|unknown, kind?: number, pred?: (context: unknown) => boolean|Promise<boolean>, hooks?: ActionHooks}} init - Initial properties describing the activity operation, loop semantics, and predicate
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.#hooks = hooks
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 operator kind name (Function or ActionWrapper).
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 wrapper).
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<{activityResult: unknown}>} - Activity result wrapper with new context
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 {unknown} hooks - Hooks instance with optional before$/after$ methods
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
  }
package/src/lib/Piper.js CHANGED
@@ -2,18 +2,20 @@
2
2
  * Generic Pipeline - Process items through a series of steps with concurrency control
3
3
  *
4
4
  * This abstraction handles:
5
- * - Concurrent processing with configurable limits
6
- * - Pipeline of processing steps
7
- * - Result categorization (success/warning/error)
5
+ * - Concurrent processing with configurable limits using worker pool pattern
6
+ * - Pipeline of processing steps executed sequentially per item
8
7
  * - Setup/cleanup lifecycle hooks
9
8
  * - Error handling and reporting
9
+ * - Dynamic worker spawning to maintain concurrency
10
10
  */
11
11
 
12
12
  import {Sass, Tantrum, Util} from "@gesslar/toolkit"
13
13
 
14
14
  export default class Piper {
15
+ /** @type {(message: string, level?: number, ...args: Array<unknown>) => void} */
15
16
  #debug
16
17
 
18
+ /** @type {Map<string, Set<unknown>>} */
17
19
  #lifeCycle = new Map([
18
20
  ["setup", new Set()],
19
21
  ["process", new Set()],
@@ -30,18 +32,23 @@ export default class Piper {
30
32
  }
31
33
 
32
34
  /**
33
- * Add a processing step to the pipeline
35
+ * Add a processing step to the pipeline.
36
+ * Each step is executed sequentially per item.
34
37
  *
35
38
  * @param {(context: unknown) => Promise<unknown>|unknown} fn Function that processes an item
36
- * @param {{name?: string, required?: boolean}} [options] Step options
39
+ * @param {{name: string, required?: boolean}} options Step options (name is required)
37
40
  * @param {unknown} [newThis] Optional this binding
38
41
  * @returns {Piper} The pipeline instance (for chaining)
42
+ * @throws {Sass} If name is not provided in options
39
43
  */
40
44
  addStep(fn, options = {}, newThis) {
45
+ if(options.name == null)
46
+ throw Sass.new("Missing name for step.")
47
+
41
48
  this.#lifeCycle.get("process").add({
42
49
  fn: fn.bind(newThis ?? this),
43
50
  name: options.name || `Step ${this.#lifeCycle.get("process").size + 1}`,
44
- required: !!options.required, // Default to required
51
+ required: options.required ?? true,
45
52
  ...options
46
53
  })
47
54
 
@@ -75,33 +82,70 @@ export default class Piper {
75
82
  }
76
83
 
77
84
  /**
78
- * Process items through the pipeline with concurrency control
85
+ * Process items through the pipeline with concurrency control using a worker pool pattern.
86
+ * Workers are spawned up to maxConcurrent limit, and as workers complete, new workers
87
+ * are spawned to maintain concurrency until all items are processed.
88
+ *
89
+ * This implementation uses dynamic worker spawning to maintain concurrency:
90
+ * - Initial workers are spawned up to maxConcurrent limit
91
+ * - As each worker completes (success OR failure), a replacement worker is spawned if items remain
92
+ * - Worker spawning occurs in finally block to ensure resilience to individual worker failures
93
+ * - All results are collected with {ok, value} or {ok: false, error} structure
94
+ * - Processing continues even if individual workers fail, collecting all errors
79
95
  *
80
96
  * @param {Array<unknown>|unknown} items - Items to process
81
- * @param {number} maxConcurrent - Maximum concurrent items to process
82
- * @returns {Promise<Array<unknown>>} - Collected results from steps
97
+ * @param {number} [maxConcurrent] - Maximum concurrent items to process (default: 10)
98
+ * @returns {Promise<Array<{ok: boolean, value?: unknown, error?: Sass}>>} - Results with success/failure status
99
+ * @throws {Sass} If setup or teardown fails
83
100
  */
84
101
  async pipe(items, maxConcurrent = 10) {
85
102
  items = Array.isArray(items)
86
103
  ? items
87
104
  : [items]
88
105
 
89
- let itemIndex = 0
90
- const allResults = []
106
+ const pipeResult = []
107
+
108
+ let pendingCount = 0
109
+ let resolveAll
110
+ const allDone = new Promise(resolve => {
111
+ resolveAll = resolve
112
+ })
91
113
 
114
+ /**
115
+ * Worker function that processes one item and potentially spawns a replacement.
116
+ * Uses shift() to atomically retrieve items from the queue, ensuring no duplicate processing.
117
+ * Spawns replacement workers in the finally block to guarantee resilience to errors.
118
+ *
119
+ * @private
120
+ */
92
121
  const processWorker = async() => {
93
- while(true) {
94
- const currentIndex = itemIndex++
95
- if(currentIndex >= items.length)
96
- break
97
-
98
- const item = items[currentIndex]
99
- try {
100
- const result = await this.#processItem(item)
101
- allResults.push(result)
102
- } catch(error) {
103
- throw Sass.new("Processing pipeline item.", error)
122
+ if(items.length === 0) {
123
+ pendingCount--
124
+
125
+ if(pendingCount === 0)
126
+ resolveAll()
127
+
128
+ return
129
+ }
130
+
131
+ const item = items.shift()
132
+
133
+ try {
134
+ const result = await this.#processWorker(item)
135
+ pipeResult.push({ok: true, value: result})
136
+ } catch(error) {
137
+ pipeResult.push({ok: false, error: Sass.new("Processing pipeline item.", error)})
138
+ } finally {
139
+ // Spawn a replacement worker if there are more items
140
+ if(items.length > 0) {
141
+ pendingCount++
142
+ processWorker() // Don't await - let it run in parallel
104
143
  }
144
+
145
+ if(--pendingCount === 0)
146
+ resolveAll()
147
+
148
+ this.#debug("pendingCount = %o", 2, pendingCount)
105
149
  }
106
150
  }
107
151
 
@@ -110,49 +154,40 @@ export default class Piper {
110
154
  )
111
155
  this.#processResult("Setting up the pipeline.", setupResult)
112
156
 
113
- // Start workers up to maxConcurrent limit
114
- const workers = []
115
- const workerCount = Math.min(maxConcurrent, items.length)
116
-
117
- for(let i = 0; i < workerCount; i++)
118
- workers.push(processWorker())
119
-
120
- // Wait for all workers to complete
121
- const processResult = await Util.settleAll(workers)
122
- this.#processResult("Processing pipeline.", processResult)
123
-
124
- // Run cleanup hooks
125
- const teardownResult = await Util.settleAll(
126
- [...this.#lifeCycle.get("teardown")].map(e => e())
127
- )
128
- this.#processResult("Tearing down the pipeline.", teardownResult)
129
-
130
- return allResults
131
- }
157
+ try {
158
+ // Start workers up to maxConcurrent limit
159
+ const workerCount = Math.min(maxConcurrent, items.length)
160
+ pendingCount = workerCount
161
+
162
+ if(workerCount === 0) {
163
+ resolveAll() // No items to process
164
+ } else {
165
+ for(let i = 0; i < workerCount; i++) {
166
+ processWorker() // Don't await - let them all run in parallel
167
+ }
168
+ }
132
169
 
133
- /**
134
- * Validate settleAll results and throw a combined error when rejected.
135
- *
136
- * @param {string} message Context message
137
- * @param {Array<unknown>} settled Results from settleAll
138
- * @private
139
- */
140
- #processResult(message, settled) {
141
- if(settled.some(r => r.status === "rejected"))
142
- throw Tantrum.new(
143
- message,
144
- settled.filter(r => r.status==="rejected").map(r => r.reason)
170
+ // Wait for all workers to complete
171
+ await allDone
172
+ } finally {
173
+ // Run cleanup hooks
174
+ const teardownResult = await Util.settleAll(
175
+ [...this.#lifeCycle.get("teardown")].map(e => e())
145
176
  )
177
+ this.#processResult("Tearing down the pipeline.", teardownResult)
178
+ }
179
+
180
+ return pipeResult
146
181
  }
147
182
 
148
183
  /**
149
- * Process a single item through all pipeline steps
184
+ * Process a single item through all pipeline steps.
150
185
  *
151
186
  * @param {unknown} item The item to process
152
187
  * @returns {Promise<unknown>} Result from the final step
153
188
  * @private
154
189
  */
155
- async #processItem(item) {
190
+ async #processWorker(item) {
156
191
  try {
157
192
  // Execute each step in sequence
158
193
  let result = item
@@ -168,4 +203,19 @@ export default class Piper {
168
203
  throw Sass.new("Processing an item.", error)
169
204
  }
170
205
  }
206
+
207
+ /**
208
+ * Validate settleAll results and throw a combined error when rejected.
209
+ *
210
+ * @param {string} message Context message
211
+ * @param {Array<unknown>} settled Results from settleAll
212
+ * @private
213
+ */
214
+ #processResult(message, settled) {
215
+ if(settled.some(r => r.status === "rejected"))
216
+ throw Tantrum.new(
217
+ message,
218
+ settled.filter(r => r.status==="rejected").map(r => r.reason)
219
+ )
220
+ }
171
221
  }
@@ -21,6 +21,8 @@
21
21
  * @property {ActionFunction|import("./ActionWrapper.js").default} op Operation to execute.
22
22
  * @property {number} [kind] Optional kind flags from {@link ActivityFlags}.
23
23
  * @property {(context: unknown) => boolean|Promise<boolean>} [pred] Loop predicate.
24
+ * @property {(context: unknown) => unknown} [splitter] Function to split context for parallel execution (SPLIT activities).
25
+ * @property {(originalContext: unknown, splitResults: unknown) => unknown} [rejoiner] Function to rejoin split results (SPLIT activities).
24
26
  */
25
27
  /**
26
28
  * @typedef {(context: unknown) => unknown|Promise<unknown>} ActionFunction
@@ -49,10 +51,16 @@ export default class ActionBuilder {
49
51
  /**
50
52
  * Creates a new ActionBuilder instance with the provided action callback.
51
53
  *
52
- * @param {ActionBuilderAction} [action] Base action invoked by the runner when a block satisfies the configured structure.
53
- * @param {ActionBuilderConfig} [config] Options
54
+ * @param {ActionBuilderAction} [action] - Base action invoked by the runner when a block satisfies the configured structure.
55
+ * @param {ActionBuilderConfig} [config] - Options
54
56
  */
55
57
  constructor(action?: ActionBuilderAction, { tag, debug }?: ActionBuilderConfig)
58
+ /**
59
+ * Get the builder's tag symbol.
60
+ *
61
+ * @returns {symbol|null} The tag symbol for this builder instance
62
+ */
63
+ get tag(): symbol | null
56
64
  /**
57
65
  * Register an activity that the runner can execute.
58
66
  *
@@ -75,6 +83,16 @@ export default class ActionBuilder {
75
83
  * @returns {ActionBuilder}
76
84
  */
77
85
  do(name: string | symbol, kind: number, pred: (context: unknown) => boolean | Promise<boolean>, op: ActionFunction | import('./ActionWrapper.js').default): ActionBuilder
86
+ /**
87
+ * @overload
88
+ * @param {string|symbol} name Activity name
89
+ * @param {number} kind Kind bitfield (ACTIVITY.SPLIT).
90
+ * @param {(context: unknown) => unknown} splitter Function to split context for parallel execution.
91
+ * @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Function to rejoin split results with original context.
92
+ * @param {ActionFunction|ActionBuilder} op Operation or nested ActionBuilder to execute on split context.
93
+ * @returns {ActionBuilder}
94
+ */
95
+ do(name: string | symbol, kind: number, splitter: (context: unknown) => unknown, rejoiner: (originalContext: unknown, splitResults: unknown) => unknown, op: ActionFunction | ActionBuilder): ActionBuilder
78
96
  /**
79
97
  * Configure hooks to be loaded from a file when the action is built.
80
98
  *
@@ -92,6 +110,14 @@ export default class ActionBuilder {
92
110
  * @throws {Sass} If hooks have already been configured.
93
111
  */
94
112
  withHooks(hooks: import('./ActionHooks.js').default): ActionBuilder
113
+ /**
114
+ * Configure hooks using an ActionHooks instance directly (typically used internally).
115
+ *
116
+ * @param {import("./ActionHooks.js").default} actionHooks Pre-configured ActionHooks instance.
117
+ * @returns {ActionBuilder} The builder instance for chaining.
118
+ * @throws {Sass} If hooks have already been configured.
119
+ */
120
+ withActionHooks(actionHooks: import('./ActionHooks.js').default): ActionBuilder
95
121
  /**
96
122
  * Finalises the builder and returns a payload that can be consumed by the
97
123
  * runner.
@@ -99,6 +125,12 @@ export default class ActionBuilder {
99
125
  * @returns {Promise<import("./ActionWrapper.js").default>} Payload consumed by the {@link ActionRunner} constructor.
100
126
  */
101
127
  build(): Promise<import('./ActionWrapper.js').default>
128
+ /**
129
+ * Check if this builder has ActionHooks configured.
130
+ *
131
+ * @returns {boolean} True if ActionHooks have been configured.
132
+ */
133
+ get hasActionHooks(): boolean
102
134
  #private
103
135
  }
104
136
  export type ActionRunner = import('./ActionRunner.js').default
@@ -149,6 +181,14 @@ export type ActivityDefinition = {
149
181
  * Loop predicate.
150
182
  */
151
183
  pred?: ((context: unknown) => boolean | Promise<boolean>) | undefined;
184
+ /**
185
+ * Function to split context for parallel execution (SPLIT activities).
186
+ */
187
+ splitter?: ((context: unknown) => unknown) | undefined;
188
+ /**
189
+ * Function to rejoin split results (SPLIT activities).
190
+ */
191
+ rejoiner?: ((originalContext: unknown, splitResults: unknown) => unknown) | undefined;
152
192
  }
153
193
  export type ActionFunction = (context: unknown) => unknown | Promise<unknown>
154
194
  //# sourceMappingURL=ActionBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../lib/ActionBuilder.js"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,uEAAuE;AAEvE;;GAEG;AAEH;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;;;;GAQG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IAaE;;;;;OAKG;IACH,qBAHW,mBAAmB,mBACnB,mBAAmB,EAe7B;;;;;;;;;;;;;IASE,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,MAC9C,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO,GACjD,aAAa,CACzB;IA4CD;;;;;;;OAOG;IACH,yBALW,MAAM,aACN,MAAM,GACJ,aAAa,CAYzB;IAED;;;;;;OAMG;IACH,iBAJW,OAAO,kBAAkB,EAAE,OAAO,GAChC,aAAa,CAWzB;IAeD;;;;;OAKG;IACH,SAFa,OAAO,CAAC,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAmBzD;;CAeF;2BA9Oa,OAAO,mBAAmB,EAAE,OAAO;4BACnC,cAAc,eAAe,EAAE,QAAQ;sBAGxC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;WAKjE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;YAYhC,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;;6BAI/C,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC"}
1
+ {"version":3,"file":"ActionBuilder.d.ts","sourceRoot":"","sources":["../../lib/ActionBuilder.js"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,uEAAuE;AAEvE;;GAEG;AAEH;;;;GAIG;AAEH;;;;GAIG;AAEH;;;;;;;;;;GAUG;AAEH;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH;IA2BE;;;;;OAKG;IACH,qBAHW,mBAAmB,mBACnB,mBAAmB,EAe7B;IA5BD;;;;OAIG;IACH,WAFa,MAAM,GAAC,IAAI,CAIvB;;;;;;;;;;;;;IA8BE,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,MAC9C,cAAc,GAAC,OAAO,oBAAoB,EAAE,OAAO,GACjD,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;IAsDD;;;;;;;OAOG;IACH,yBALW,MAAM,aACN,MAAM,GACJ,aAAa,CAUzB;IAED;;;;;;OAMG;IACH,iBAJW,OAAO,kBAAkB,EAAE,OAAO,GAChC,aAAa,CASzB;IAED;;;;;;OAMG;IACH,6BAJW,OAAO,kBAAkB,EAAE,OAAO,GAChC,aAAa,CASzB;IA2BD;;;;;OAKG;IACH,SAFa,OAAO,CAAC,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAiBzD;IAED;;;;OAIG;IACH,sBAFa,OAAO,CAInB;;CAiCF;2BAlUa,OAAO,mBAAmB,EAAE,OAAO;4BACnC,cAAc,eAAe,EAAE,QAAQ;sBAGxC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;WAKjE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;YAYhC,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;;;;0BACpC,OAAO,KAAK,OAAO;;;;kCACX,OAAO,gBAAgB,OAAO,KAAK,OAAO;;6BAI7D,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAC,OAAO,CAAC,OAAO,CAAC"}
@@ -3,11 +3,10 @@
3
3
  */
4
4
  /**
5
5
  * @typedef {object} ActionHooksConfig
6
- * @property {string} actionKind Action identifier shared between runner and hooks.
7
- * @property {FileObject} hooksFile File handle used to import the hooks module.
8
- * @property {unknown} [hooks] Already-instantiated hooks implementation (skips loading).
6
+ * @property {string} [actionKind] Action identifier shared between runner and hooks.
7
+ * @property {FileObject|string} [hooksFile] File handle or path used to import the hooks module.
8
+ * @property {unknown} [hooksObject] Already-instantiated hooks implementation (skips loading).
9
9
  * @property {number} [hookTimeout] Timeout applied to hook execution in milliseconds.
10
- * @property {DebugFn} debug Logger to emit diagnostics.
11
10
  */
12
11
  /**
13
12
  * @typedef {Record<string, (context: unknown) => Promise<unknown>|unknown>} HookModule
@@ -21,37 +20,38 @@ export default class ActionHooks {
21
20
  /**
22
21
  * Static factory method to create and initialize a hook manager.
23
22
  * Loads hooks from the specified file and returns an initialized instance.
24
- * Override loadHooks() in subclasses to customize hook loading logic.
23
+ * If a hooksObject is provided in config, it's used directly; otherwise, hooks are loaded from file.
25
24
  *
26
- * @param {ActionHooksConfig} config Same configuration object as constructor
25
+ * @param {ActionHooksConfig} config Configuration object with hooks settings
27
26
  * @param {DebugFn} debug The debug function.
28
- * @returns {Promise<ActionHooks|null>} Initialized hook manager or null if no hooks found
27
+ * @returns {Promise<ActionHooks>} Initialized hook manager
29
28
  */
30
- static 'new'(config: ActionHooksConfig, debug: DebugFn): Promise<ActionHooks | null>
29
+ static 'new'(config: ActionHooksConfig, debug: DebugFn): Promise<ActionHooks>
31
30
  /**
32
31
  * Creates a new ActionHook instance.
33
32
  *
34
33
  * @param {ActionHooksConfig} config Configuration values describing how to load the hooks.
34
+ * @param {(message: string, level?: number, ...args: Array<unknown>) => void} debug Debug function
35
35
  */
36
- constructor({ actionKind, hooksFile, hooks, hookTimeout, debug }: ActionHooksConfig)
36
+ constructor({ actionKind, hooksFile, hooksObject, hookTimeout }: ActionHooksConfig, debug: (message: string, level?: number, ...args: Array<unknown>) => void)
37
37
  /**
38
38
  * Gets the action identifier.
39
39
  *
40
- * @returns {string} Action identifier or instance
40
+ * @returns {string|null} Action identifier or instance
41
41
  */
42
- get actionKind(): string
42
+ get actionKind(): string | null
43
43
  /**
44
44
  * Gets the hooks file object.
45
45
  *
46
- * @returns {FileObject} File object containing hooks
46
+ * @returns {FileObject|null} File object containing hooks
47
47
  */
48
- get hooksFile(): FileObject
48
+ get hooksFile(): FileObject | null
49
49
  /**
50
50
  * Gets the loaded hooks object.
51
51
  *
52
- * @returns {object|null} Hooks object or null if not loaded
52
+ * @returns {HookModule|null} Hooks object or null if not loaded
53
53
  */
54
- get hooks(): object | null
54
+ get hooks(): HookModule | null
55
55
  /**
56
56
  * Gets the hook execution timeout in milliseconds.
57
57
  *
@@ -71,12 +71,15 @@ export default class ActionHooks {
71
71
  */
72
72
  get cleanup(): (args: object) => unknown | null
73
73
  /**
74
- * Invoke a dynamically-named hook such as `before$foo`.
74
+ * Invoke a dynamically-named hook such as `before$foo` or `after$foo`.
75
+ * The hook name is constructed by combining the kind with the activity name.
76
+ * Symbols are converted to their description. Non-alphanumeric characters are filtered out.
75
77
  *
76
78
  * @param {'before'|'after'|'setup'|'cleanup'|string} kind Hook namespace.
77
79
  * @param {string|symbol} activityName Activity identifier.
78
80
  * @param {unknown} context Pipeline context supplied to the hook.
79
81
  * @returns {Promise<void>}
82
+ * @throws {Sass} If the hook execution fails or exceeds timeout.
80
83
  */
81
84
  callHook(kind: 'before' | 'after' | 'setup' | 'cleanup' | string, activityName: string | symbol, context: unknown): Promise<void>
82
85
  #private
@@ -86,23 +89,19 @@ export type ActionHooksConfig = {
86
89
  /**
87
90
  * Action identifier shared between runner and hooks.
88
91
  */
89
- actionKind: string;
92
+ actionKind?: string | undefined;
90
93
  /**
91
- * File handle used to import the hooks module.
94
+ * File handle or path used to import the hooks module.
92
95
  */
93
- hooksFile: FileObject;
96
+ hooksFile?: string | FileObject | undefined;
94
97
  /**
95
98
  * Already-instantiated hooks implementation (skips loading).
96
99
  */
97
- hooks?: unknown;
100
+ hooksObject?: unknown;
98
101
  /**
99
102
  * Timeout applied to hook execution in milliseconds.
100
103
  */
101
104
  hookTimeout?: number | undefined;
102
- /**
103
- * Logger to emit diagnostics.
104
- */
105
- debug: DebugFn;
106
105
  }
107
106
  export type HookModule = Record<string, (context: unknown) => Promise<unknown> | unknown>
108
107
  import { FileObject } from '@gesslar/toolkit'
@@ -1 +1 @@
1
- {"version":3,"file":"ActionHooks.d.ts","sourceRoot":"","sources":["../../lib/ActionHooks.js"],"names":[],"mappings":"AAGA;;GAEG;AAEH;;;;;;;GAOG;AAEH;;GAEG;AAEH;;;;GAIG;AACH;IA+EE;;;;;;;;OAQG;IACH,qBAJW,iBAAiB,SACjB,OAAO,GACL,OAAO,CAAC,WAAW,GAAC,IAAI,CAAC,CA2CrC;IArHD;;;;OAIG;IACH,kEAFW,iBAAiB,EAQ3B;IAED;;;;OAIG;IACH,kBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,iBAFa,UAAU,CAItB;IAED;;;;OAIG;IACH,aAFa,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,eAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,aAFa,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAC,IAAI,CAI1C;IAED;;;;OAIG;IACH,eAFa,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAC,IAAI,CAI1C;IAsDD;;;;;;;OAOG;IACH,eALW,QAAQ,GAAC,OAAO,GAAC,OAAO,GAAC,SAAS,GAAC,MAAM,gBACzC,MAAM,GAAC,MAAM,WACb,OAAO,GACL,OAAO,CAAC,IAAI,CAAC,CAwDzB;;CAmBF;sBAzOY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;gBAKjE,MAAM;;;;eACN,UAAU;;;;YACV,OAAO;;;;;;;;WAEP,OAAO;;yBAIR,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,CAAC;2BAhBzB,kBAAkB"}
1
+ {"version":3,"file":"ActionHooks.d.ts","sourceRoot":"","sources":["../../lib/ActionHooks.js"],"names":[],"mappings":"AAGA;;GAEG;AAEH;;;;;;GAMG;AAEH;;GAEG;AAEH;;;;GAIG;AACH;IAmFE;;;;;;;;OAQG;IACH,qBAJW,iBAAiB,SACjB,OAAO,GACL,OAAO,CAAC,WAAW,CAAC,CA2ChC;IAzHD;;;;;OAKG;IACH,iEAHW,iBAAiB,SACjB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,EAW5E;IAED;;;;OAIG;IACH,kBAFa,MAAM,GAAC,IAAI,CAIvB;IAED;;;;OAIG;IACH,iBAFa,UAAU,GAAC,IAAI,CAI3B;IAED;;;;OAIG;IACH,aAFa,UAAU,GAAC,IAAI,CAI3B;IAED;;;;OAIG;IACH,eAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,aAFa,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAC,IAAI,CAI1C;IAED;;;;OAIG;IACH,eAFa,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAC,IAAI,CAI1C;IAsDD;;;;;;;;;;OAUG;IACH,eANW,QAAQ,GAAC,OAAO,GAAC,OAAO,GAAC,SAAS,GAAC,MAAM,gBACzC,MAAM,GAAC,MAAM,WACb,OAAO,GACL,OAAO,CAAC,IAAI,CAAC,CAyDzB;;CA6BF;sBAzPY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;;;;;;;;;kBAOjE,OAAO;;;;;;yBAKR,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAC,OAAO,CAAC;2BAfzB,kBAAkB"}
@@ -22,10 +22,12 @@ export default class ActionRunner extends Piper {
22
22
  constructor(actionBuilder: import('./ActionBuilder.js').default | null, { debug }?: ActionRunnerOptions)
23
23
  /**
24
24
  * Executes the configured action pipeline.
25
+ * Builds the ActionWrapper on first run and caches it for subsequent calls.
26
+ * Supports WHILE, UNTIL, and SPLIT activity kinds.
25
27
  *
26
28
  * @param {unknown} context - Seed value passed to the first activity.
27
- * @returns {Promise<unknown>} Final value produced by the pipeline, or null when a parallel stage reports failures.
28
- * @throws {Sass} When no activities are registered or required parallel builders are missing.
29
+ * @returns {Promise<unknown>} Final value produced by the pipeline.
30
+ * @throws {Sass} When no activities are registered, conflicting activity kinds are used, or execution fails.
29
31
  */
30
32
  run(context: unknown): Promise<unknown>
31
33
  #private
@@ -1 +1 @@
1
- {"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../lib/ActionRunner.js"],"names":[],"mappings":"AAMA;;GAEG;AAEH;;;GAGG;AACH;;;;;;GAMG;AACH;IAUE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAgB7B;IAED;;;;;;OAMG;IACH,aAJW,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,CA+C5B;;CA2CF;sBA5IY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;;;kBAH7D,YAAY"}
1
+ {"version":3,"file":"ActionRunner.d.ts","sourceRoot":"","sources":["../../lib/ActionRunner.js"],"names":[],"mappings":"AAMA;;GAEG;AAEH;;;GAGG;AACH;;;;;;GAMG;AACH;IAaE;;;;;OAKG;IACH,2BAHW,OAAO,oBAAoB,EAAE,OAAO,GAAC,IAAI,cACzC,mBAAmB,EAkB7B;IAED;;;;;;;;OAQG;IACH,aAJW,OAAO,GACL,OAAO,CAAC,OAAO,CAAC,CA4E5B;;CAwFF;sBA7NY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;;;;;;;kBAH7D,YAAY"}