@gesslar/actioneer 2.2.0 → 2.4.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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "gesslar",
6
6
  "url": "https://gesslar.dev"
7
7
  },
8
- "version": "2.2.0",
8
+ "version": "2.4.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -22,7 +22,6 @@
22
22
  "composition",
23
23
  "lasagna"
24
24
  ],
25
- "main": "./src/index.js",
26
25
  "type": "module",
27
26
  "exports": {
28
27
  ".": {
@@ -46,28 +45,28 @@
46
45
  ],
47
46
  "sideEffects": false,
48
47
  "engines": {
49
- "node": ">=22"
50
- },
51
- "dependencies": {
52
- "@gesslar/toolkit": "^3.24.0"
53
- },
54
- "devDependencies": {
55
- "@gesslar/uglier": "^1.2.0",
56
- "eslint": "^9.39.2",
57
- "typescript": "^5.9.3"
48
+ "node": ">=24.13.0"
58
49
  },
59
50
  "scripts": {
60
51
  "lint": "eslint src/",
61
52
  "lint:fix": "eslint src/ --fix",
62
53
  "types": "node -e \"require('fs').rmSync('types',{recursive:true,force:true});\" && tsc -p tsconfig.types.json",
63
- "submit": "pnpm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
64
- "update": "pnpm self-update && pnpx npm-check-updates -u && pnpm install",
54
+ "submit": "npm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
55
+ "update": "npx npm-check-updates -u && npm install",
65
56
  "test": "node --test tests/**/*.test.js",
66
57
  "test:browser": "node --test tests/browser/*.test.js",
67
58
  "test:node": "node --test tests/node/*.test.js",
68
59
  "pr": "gt submit -p --ai",
69
- "patch": "pnpm version patch",
70
- "minor": "pnpm version minor",
71
- "major": "pnpm version major"
60
+ "patch": "npm version patch",
61
+ "minor": "npm version minor",
62
+ "major": "npm version major"
63
+ },
64
+ "dependencies": {
65
+ "@gesslar/toolkit": "^3.37.0"
66
+ },
67
+ "devDependencies": {
68
+ "@gesslar/uglier": "^1.6.2",
69
+ "eslint": "^10.0.0",
70
+ "typescript": "^5.9.3"
72
71
  }
73
- }
72
+ }
@@ -4,36 +4,29 @@ import ActionWrapper from "./ActionWrapper.js"
4
4
  import ActionHooks from "./ActionHooks.js"
5
5
  import {ACTIVITY} from "./Activity.js"
6
6
 
7
- /** @typedef {import("./ActionRunner.js").default} ActionRunner */
8
- /** @typedef {typeof import("./Activity.js").ACTIVITY} ActivityFlags */
9
-
10
7
  /**
8
+ * Type imports and definitions.
9
+ *
10
+ * @import {default as ActionRunner} from "./ActionRunner.js"
11
+ *
11
12
  * @typedef {(message: string, level?: number, ...args: Array<unknown>) => void} DebugFn
12
- */
13
-
14
- /**
13
+ *
15
14
  * @typedef {object} ActionBuilderAction
16
- * @property {(builder: ActionBuilder) => void} setup Function invoked during {@link ActionBuilder#build} to register activities.
17
- * @property {symbol} [tag] Optional tag to reuse when reconstructing builders.
18
- */
19
-
20
- /**
15
+ * @property {(builder: ActionBuilder) => void} setup - Function invoked during {@link ActionBuilder} to register activities.
16
+ * @property {symbol} [tag] - Optional tag to reuse when reconstructing builders.
17
+ *
21
18
  * @typedef {object} ActionBuilderConfig
22
- * @property {symbol} [tag] Optional tag for the builder instance.
23
- * @property {DebugFn} [debug] Logger used by the pipeline internals.
24
- */
25
-
26
- /**
19
+ * @property {symbol} [tag] - Optional tag for the builder instance.
20
+ * @property {DebugFn} [debug] - Logger used by the pipeline internals.
21
+ *
27
22
  * @typedef {object} ActivityDefinition
28
- * @property {ActionBuilderAction|null} action Parent action instance when available.
29
- * @property {DebugFn|null} debug Logger function.
30
- * @property {string|symbol} name Activity identifier.
31
- * @property {ActionFunction|import("./ActionWrapper.js").default} op Operation to execute.
32
- * @property {number} [kind] Optional kind flags from {@link ActivityFlags}.
33
- * @property {(context: unknown) => boolean|Promise<boolean>} [pred] Loop predicate.
34
- */
35
-
36
- /**
23
+ * @property {ActionBuilderAction|null} action - Parent action instance when available.
24
+ * @property {DebugFn|null} debug - Logger function.
25
+ * @property {string|symbol} name - Activity identifier.
26
+ * @property {ActionFunction|import("./ActionWrapper.js").default} op - Operation to execute.
27
+ * @property {number} [kind] - Optional kind flags from {@link ACTIVITY}.
28
+ * @property {(context: unknown) => boolean|Promise<boolean>} [pred] - Loop predicate.
29
+ *
37
30
  * @typedef {(context: unknown) => unknown|Promise<unknown>} ActionFunction
38
31
  */
39
32
 
@@ -58,28 +51,28 @@ import {ACTIVITY} from "./Activity.js"
58
51
  * @class ActionBuilder
59
52
  */
60
53
  export default class ActionBuilder {
61
- /** @type {ActionBuilderAction|null} */
54
+ /** @type {ActionBuilderAction?} */
62
55
  #action = null
63
56
  /** @type {Map<string|symbol, ActivityDefinition>} */
64
57
  #activities = new Map([])
65
- /** @type {DebugFn|null} */
58
+ /** @type {DebugFn?} */
66
59
  #debug = null
67
- /** @type {symbol|null} */
60
+ /** @type {symbol?} */
68
61
  #tag = null
69
- /** @type {string|null} */
62
+ /** @type {string?} */
70
63
  #hooksFile = null
71
- /** @type {string|null} */
64
+ /** @type {string?} */
72
65
  #hooksKind = null
73
- /** @type {import("./ActionHooks.js").default|null} */
66
+ /** @type {ActionHooks?} */
74
67
  #hooks = null
75
- /** @type {ActionFunction|null} */
68
+ /** @type {ActionFunction?} */
76
69
  #done = null
77
70
 
78
71
  /**
79
72
  * Creates a new ActionBuilder instance with the provided action callback.
80
73
  *
81
- * @param {ActionBuilderAction} [action] Base action invoked by the runner when a block satisfies the configured structure.
82
- * @param {ActionBuilderConfig} [config] Options
74
+ * @param {ActionBuilderAction} [action] - Base action invoked by the runner when a block satisfies the configured structure.
75
+ * @param {ActionBuilderConfig} [config] - Options
83
76
  */
84
77
  constructor(
85
78
  action,
@@ -89,6 +82,9 @@ export default class ActionBuilder {
89
82
  this.#tag = this.#tag || tag
90
83
 
91
84
  if(action) {
85
+ if(action.tag)
86
+ throw Sass.new("Action has already been consumed by a builder and cannot be reused.")
87
+
92
88
  if(Data.typeOf(action.setup) !== "Function")
93
89
  throw Sass.new("Setup must be a function.")
94
90
 
@@ -110,44 +106,44 @@ export default class ActionBuilder {
110
106
  * - do(name, kind, splitter, rejoiner, opOrWrapper) - SPLIT with parallel execution
111
107
  *
112
108
  * @overload
113
- * @param {string|symbol} name Activity name
114
- * @param {ActionFunction} op Operation to execute once.
109
+ * @param {string|symbol} name - Activity name
110
+ * @param {ActionFunction} op - Operation to execute once.
115
111
  * @returns {ActionBuilder}
116
112
  */
117
113
 
118
114
  /**
119
115
  * @overload
120
- * @param {string|symbol} name Activity name
121
- * @param {number} kind ACTIVITY.BREAK or ACTIVITY.CONTINUE flag.
122
- * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate to evaluate for control flow.
116
+ * @param {string|symbol} name - Activity name
117
+ * @param {number} kind - ACTIVITY.BREAK or ACTIVITY.CONTINUE flag.
118
+ * @param {(context: unknown) => boolean|Promise<boolean>} pred - Predicate to evaluate for control flow.
123
119
  * @returns {ActionBuilder}
124
120
  */
125
121
 
126
122
  /**
127
123
  * @overload
128
- * @param {string|symbol} name Activity name
129
- * @param {number} kind Activity kind (WHILE, UNTIL, or IF) from {@link ActivityFlags}.
130
- * @param {(context: unknown) => boolean|Promise<boolean>} pred Predicate executed before/after the op.
131
- * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
124
+ * @param {string|symbol} name - Activity name
125
+ * @param {number} kind - Activity kind (WHILE, UNTIL, or IF) from {@link ACTIVITY}.
126
+ * @param {(context: unknown) => boolean|Promise<boolean>} pred - Predicate executed before/after the op.
127
+ * @param {ActionFunction|ActionBuilder} op - Operation or nested builder to execute.
132
128
  * @returns {ActionBuilder}
133
129
  */
134
130
 
135
131
  /**
136
132
  * @overload
137
- * @param {string|symbol} name Activity name
138
- * @param {number} kind ACTIVITY.SPLIT flag.
139
- * @param {(context: unknown) => unknown} splitter Splitter function for SPLIT mode.
140
- * @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner Rejoiner function for SPLIT mode.
141
- * @param {ActionFunction|ActionBuilder} op Operation or nested builder to execute.
133
+ * @param {string|symbol} name - Activity name
134
+ * @param {number} kind - ACTIVITY.SPLIT flag.
135
+ * @param {(context: unknown) => unknown} splitter - Splitter function for SPLIT mode.
136
+ * @param {(originalContext: unknown, splitResults: unknown) => unknown} rejoiner - Rejoiner function for SPLIT mode.
137
+ * @param {ActionFunction|ActionBuilder} op - Operation or nested builder to execute.
142
138
  * @returns {ActionBuilder}
143
139
  */
144
140
 
145
141
  /**
146
142
  * Handles runtime dispatch across the documented overloads.
147
143
  *
148
- * @param {string|symbol} name Activity name
149
- * @param {...unknown} args See overloads
150
- * @returns {ActionBuilder} The builder instance for chaining
144
+ * @param {string|symbol} name - Activity name
145
+ * @param {...unknown} args - See overloads
146
+ * @returns {ActionBuilder} - The builder instance for chaining
151
147
  */
152
148
  do(name, ...args) {
153
149
  this.#dupeActivityCheck(name)
@@ -209,9 +205,9 @@ export default class ActionBuilder {
209
205
  /**
210
206
  * Configure hooks to be loaded from a file when the action is built.
211
207
  *
212
- * @param {string} hooksFile Path to the hooks module file.
213
- * @param {string} hooksKind Name of the exported hooks class to instantiate.
214
- * @returns {ActionBuilder} The builder instance for chaining.
208
+ * @param {string} hooksFile - Path to the hooks module file.
209
+ * @param {string} hooksKind - Name of the exported hooks class to instantiate.
210
+ * @returns {ActionBuilder} - The builder instance for chaining.
215
211
  * @throws {Sass} If hooks have already been configured.
216
212
  */
217
213
  withHooksFile(hooksFile, hooksKind) {
@@ -228,15 +224,15 @@ export default class ActionBuilder {
228
224
  /**
229
225
  * Configure hooks using a pre-instantiated hooks object.
230
226
  *
231
- * @param {import("./ActionHooks.js").default} hooks An already-instantiated hooks instance.
232
- * @returns {ActionBuilder} The builder instance for chaining.
227
+ * @param {ActionHooks} hooks - An already-instantiated hooks instance.
228
+ * @returns {ActionBuilder} - The builder instance for chaining.
233
229
  * @throws {Sass} If hooks have already been configured with a different instance.
234
230
  */
235
231
  withHooks(hooks) {
236
- // If the same hooks instance is already set, this is idempotent - just return
237
- if(this.#hooks === hooks) {
232
+ // If the same hooks instance is already set, this is idempotent -just
233
+ // return.
234
+ if(this.#hooks === hooks)
238
235
  return this
239
- }
240
236
 
241
237
  Valid.assert(this.#hooksFile === null, "Hooks have already been configured.")
242
238
  Valid.assert(this.#hooksKind === null, "Hooks have already been configured.")
@@ -251,7 +247,7 @@ export default class ActionBuilder {
251
247
  * Configure the action instance if not already set.
252
248
  * Used to propagate parent action context to nested builders.
253
249
  *
254
- * @param {ActionBuilderAction} action The action instance to inherit.
250
+ * @param {ActionBuilderAction} action - The action instance to inherit.
255
251
  * @returns {ActionBuilder} The builder instance for chaining.
256
252
  */
257
253
  withAction(action) {
@@ -271,7 +267,7 @@ export default class ActionBuilder {
271
267
  /**
272
268
  * Register a callback to be executed after all activities complete.
273
269
  *
274
- * @param {ActionFunction} callback Function to execute at the end of the pipeline.
270
+ * @param {ActionFunction} callback - Function to execute at the end of the pipeline.
275
271
  * @returns {ActionBuilder} The builder instance for chaining.
276
272
  */
277
273
  done(callback) {
@@ -285,7 +281,7 @@ export default class ActionBuilder {
285
281
  * Validates that an activity name has not been reused.
286
282
  *
287
283
  * @private
288
- * @param {string | symbol} name Activity identifier.
284
+ * @param {string|symbol} name Activity identifier.
289
285
  */
290
286
  #dupeActivityCheck(name) {
291
287
  Valid.assert(
@@ -298,9 +294,10 @@ export default class ActionBuilder {
298
294
  * Finalises the builder and returns a payload that can be consumed by the
299
295
  * runner.
300
296
  *
301
- * @returns {Promise<import("./ActionWrapper.js").default>} Payload consumed by the {@link ActionRunner} constructor.
297
+ * @param {ActionRunner} runner - The runner invoking the build.
298
+ * @returns {Promise<ActionWrapper>} Payload consumed by the {@link ActionRunner} constructor.
302
299
  */
303
- async build() {
300
+ async build(runner) {
304
301
  const action = this.#action
305
302
 
306
303
  if(action && !action.tag) {
@@ -309,6 +306,20 @@ export default class ActionBuilder {
309
306
  await Promise.resolve(action.setup.call(action, this))
310
307
  }
311
308
 
309
+ if(action) {
310
+ // Inject a method to the action for emission, but only if it's undefined.
311
+ if(Data.isType(action.emit, "Undefined"))
312
+ action.emit = (...args) => runner.emit(...args)
313
+
314
+ // Inject a method to the action for onission, but only if it's undefined.
315
+ if(Data.isType(action.on, "Undefined"))
316
+ action.on = (event, cb) => runner.on(event, cb)
317
+
318
+ // Inject a method to the action for offission, but only if it's undefined.
319
+ if(Data.isType(action.off, "Undefined"))
320
+ action.off = (event, cb) => runner.off(event, cb)
321
+ }
322
+
312
323
  // All children in a branch also get the same hooks.
313
324
  const hooks = await this.#getHooks()
314
325
 
@@ -317,7 +328,8 @@ export default class ActionBuilder {
317
328
  debug: this.#debug,
318
329
  hooks,
319
330
  done: this.#done,
320
- action: this.#action,
331
+ action,
332
+ runner: runner,
321
333
  })
322
334
  }
323
335
 
@@ -340,4 +352,14 @@ export default class ActionBuilder {
340
352
  if(hooksFile && hooksKind)
341
353
  return await newHooks({hooksFile,hooksKind}, this.#debug)
342
354
  }
355
+
356
+ /**
357
+ * Returns the raw hooks value configured on this builder.
358
+ * Used by {@link ActionRunner} to access setup/cleanup lifecycle hooks.
359
+ *
360
+ * @returns {object|Function|null} Raw hooks value, or null if not configured.
361
+ */
362
+ get hooks() {
363
+ return this.#hooks
364
+ }
343
365
  }
@@ -4,16 +4,14 @@ import {Data, Sass, Promised, Time, Util, Valid} from "@gesslar/toolkit"
4
4
  * @typedef {(message: string, level?: number, ...args: Array<unknown>) => void} DebugFn
5
5
  */
6
6
 
7
- /**
8
- * @typedef {object} ActionHooksConfig
9
- * @property {string} actionKind Action identifier shared between runner and hooks.
10
- * @property {unknown} hooks Already-instantiated hooks implementation.
11
- * @property {number} [hookTimeout] Timeout applied to hook execution in milliseconds.
12
- * @property {DebugFn} debug Logger to emit diagnostics.
13
- */
14
-
15
7
  /**
16
8
  * @typedef {Record<string, (context: unknown) => Promise<unknown>|unknown>} HookModule
9
+ *
10
+ * @typedef {object} ActionHooksConfig
11
+ * @property {string} actionKind - Action identifier shared between runner and hooks.
12
+ * @property {object|Function|null} hooks - Pre-instantiated hooks object, a constructor to instantiate, or null.
13
+ * @property {number} [hookTimeout] - Timeout applied to hook execution in milliseconds.
14
+ * @property {DebugFn} debug - Logger to emit diagnostics.
17
15
  */
18
16
 
19
17
  /**
@@ -24,19 +22,19 @@ import {Data, Sass, Promised, Time, Util, Valid} from "@gesslar/toolkit"
24
22
  * Browser version: Requires pre-instantiated hooks. File-based loading is not supported.
25
23
  */
26
24
  export default class ActionHooks {
27
- /** @type {HookModule|null} */
25
+ /** @type {HookModule?} */
28
26
  #hooks = null
29
- /** @type {string|null} */
27
+ /** @type {string?} */
30
28
  #actionKind = null
31
29
  /** @type {number} */
32
30
  #timeout = 1_000 // Default 1 second timeout
33
- /** @type {DebugFn|null} */
31
+ /** @type {DebugFn?} */
34
32
  #debug = null
35
33
 
36
34
  /**
37
35
  * Creates a new ActionHook instance.
38
36
  *
39
- * @param {ActionHooksConfig} config Configuration values describing how to load the hooks.
37
+ * @param {ActionHooksConfig} config - Configuration values describing how to load the hooks.
40
38
  */
41
39
  constructor({actionKind, hooks, hookTimeout = 1_000, debug}) {
42
40
  this.#actionKind = actionKind
@@ -56,10 +54,18 @@ export default class ActionHooks {
56
54
 
57
55
  /**
58
56
  * Gets the loaded hooks object.
57
+ * If the stored value is a plain object it is returned as-is.
58
+ * If it is a constructor function a new instance is created and returned.
59
59
  *
60
- * @returns {object|null} Hooks object or null if not loaded
60
+ * @returns {object?} Hooks object or null if not loaded
61
61
  */
62
62
  get hooks() {
63
+ if(Data.isType(this.#hooks, "Object"))
64
+ return this.#hooks
65
+
66
+ else if(Data.isType(this.#hooks, "Function"))
67
+ return new this.#hooks()
68
+
63
69
  return this.#hooks
64
70
  }
65
71
 
@@ -75,7 +81,7 @@ export default class ActionHooks {
75
81
  /**
76
82
  * Gets the setup hook function if available.
77
83
  *
78
- * @returns {(args: object) => unknown|null} Setup hook function or null
84
+ * @returns {(args: object) => unknown} Setup hook function or null
79
85
  */
80
86
  get setup() {
81
87
  return this.hooks?.setup || null
@@ -84,7 +90,7 @@ export default class ActionHooks {
84
90
  /**
85
91
  * Gets the cleanup hook function if available.
86
92
  *
87
- * @returns {(args: object) => unknown|null} Cleanup hook function or null
93
+ * @returns {(args: object) => unknown} Cleanup hook function or null
88
94
  */
89
95
  get cleanup() {
90
96
  return this.hooks?.cleanup || null
@@ -94,12 +100,12 @@ export default class ActionHooks {
94
100
  * Static factory method to create and initialize a hook manager.
95
101
  * Browser version: Only works with pre-instantiated hooks passed via config.hooks.
96
102
  *
97
- * @param {ActionHooksConfig} config Configuration object with hooks property
98
- * @param {DebugFn} debug The debug function.
99
- * @returns {Promise<ActionHooks|null>} Initialized hook manager or null if no hooks provided
103
+ * @param {ActionHooksConfig} config - Configuration object with hooks property
104
+ * @param {DebugFn} debug - The debug function.
105
+ * @returns {Promise<ActionHooks?>} Initialized hook manager or null if no hooks provided
100
106
  */
101
107
  static async new(config, debug) {
102
- debug("Creating new HookManager instance with args: %o", 2, config)
108
+ debug("Creating new ActionHooks instance with args: %o", 2, config)
103
109
 
104
110
  if(!config.hooks) {
105
111
  debug("No hooks provided (browser mode requires pre-instantiated hooks)", 2)
@@ -117,12 +123,13 @@ export default class ActionHooks {
117
123
  /**
118
124
  * Invoke a dynamically-named hook such as `before$foo`.
119
125
  *
120
- * @param {'before'|'after'|'setup'|'cleanup'|string} kind Hook namespace.
121
- * @param {string|symbol} activityName Activity identifier.
122
- * @param {unknown} context Pipeline context supplied to the hook.
126
+ * @param {string} kind - Hook namespace.
127
+ * @param {string|symbol} activityName - Activity identifier.
128
+ * @param {unknown} oldContext - Pipeline context supplied to the hook.
129
+ * @param {unknown} newContext - For after$ hooks, the result of the op
123
130
  * @returns {Promise<void>}
124
131
  */
125
- async callHook(kind, activityName, context) {
132
+ async callHook(kind, activityName, oldContext, newContext) {
126
133
  try {
127
134
  const debug = this.#debug
128
135
  const hooks = this.#hooks
@@ -149,7 +156,11 @@ export default class ActionHooks {
149
156
  debug("Hook function starting execution: %o", 4, hookName)
150
157
 
151
158
  const duration = (
152
- await Util.time(() => hook.call(this.#hooks, context))
159
+ newContext
160
+ ? await Util.time(
161
+ () => hook.call(this.#hooks, newContext, oldContext)
162
+ )
163
+ : await Util.time(() => hook.call(this.#hooks, oldContext))
153
164
  ).cost
154
165
 
155
166
  debug("Hook function completed successfully: %o, after %oms", 4, hookName, duration)
@@ -170,9 +181,6 @@ export default class ActionHooks {
170
181
  } catch(error) {
171
182
  throw Sass.new(`Processing hook ${kind}$${activityName}`, error)
172
183
  }
173
-
174
- debug("We made it throoough the wildernessss", 4)
175
-
176
184
  } catch(error) {
177
185
  throw Sass.new(`Processing hook ${kind}$${activityName}`, error)
178
186
  }