@gesslar/toolkit 0.5.0 → 0.7.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.
Files changed (66) hide show
  1. package/package.json +8 -8
  2. package/src/lib/Collection.js +132 -17
  3. package/src/lib/Contract.js +1 -1
  4. package/src/lib/Data.js +27 -18
  5. package/src/lib/DirectoryObject.js +1 -1
  6. package/src/lib/FS.js +10 -0
  7. package/src/lib/Glog.js +27 -10
  8. package/src/lib/Logger.js +3 -0
  9. package/src/lib/Sass.js +6 -1
  10. package/src/lib/Tantrum.js +43 -0
  11. package/src/lib/TypeSpec.js +11 -7
  12. package/src/lib/Util.js +82 -0
  13. package/src/lib/Valid.js +24 -6
  14. package/src/types/Collection.d.ts +6 -1
  15. package/src/types/Contract.d.ts +27 -27
  16. package/src/types/Data.d.ts +23 -23
  17. package/src/types/FS.d.ts +3 -3
  18. package/src/types/Glog.d.ts +302 -49
  19. package/src/types/Sass.d.ts +1 -1
  20. package/src/types/Schemer.d.ts +29 -29
  21. package/src/types/Tantrum.d.ts +10 -10
  22. package/src/types/Term.d.ts +1 -1
  23. package/src/types/Terms.d.ts +21 -21
  24. package/src/types/Type.d.ts +1 -1
  25. package/src/types/Util.d.ts +20 -2
  26. package/src/types/index.d.ts +17 -23
  27. package/src/types/index.d.ts.map +1 -0
  28. package/src/types/lib/Cache.d.ts +28 -0
  29. package/src/types/lib/Cache.d.ts.map +1 -0
  30. package/src/types/lib/Collection.d.ts +246 -0
  31. package/src/types/lib/Collection.d.ts.map +1 -0
  32. package/src/types/lib/Contract.d.ts +72 -0
  33. package/src/types/lib/Contract.d.ts.map +1 -0
  34. package/src/types/lib/Data.d.ts +189 -0
  35. package/src/types/lib/Data.d.ts.map +1 -0
  36. package/src/types/lib/DirectoryObject.d.ts +148 -0
  37. package/src/types/lib/DirectoryObject.d.ts.map +1 -0
  38. package/src/types/lib/FS.d.ts +70 -0
  39. package/src/types/lib/FS.d.ts.map +1 -0
  40. package/src/types/lib/FileObject.d.ts +189 -0
  41. package/src/types/lib/FileObject.d.ts.map +1 -0
  42. package/src/types/lib/Glog.d.ts +113 -0
  43. package/src/types/lib/Glog.d.ts.map +1 -0
  44. package/src/types/lib/Logger.d.ts +46 -0
  45. package/src/types/lib/Logger.d.ts.map +1 -0
  46. package/src/types/lib/Sass.d.ts +62 -0
  47. package/src/types/lib/Sass.d.ts.map +1 -0
  48. package/src/types/lib/Schemer.d.ts +23 -0
  49. package/src/types/lib/Schemer.d.ts.map +1 -0
  50. package/src/types/lib/Tantrum.d.ts +50 -0
  51. package/src/types/lib/Tantrum.d.ts.map +1 -0
  52. package/src/types/lib/Term.d.ts +103 -0
  53. package/src/types/lib/Term.d.ts.map +1 -0
  54. package/src/types/lib/Terms.d.ts +24 -0
  55. package/src/types/lib/Terms.d.ts.map +1 -0
  56. package/src/types/lib/TypeSpec.d.ts +92 -0
  57. package/src/types/lib/TypeSpec.d.ts.map +1 -0
  58. package/src/types/lib/Util.d.ts +197 -0
  59. package/src/types/lib/Util.d.ts.map +1 -0
  60. package/src/types/lib/Valid.d.ts +33 -0
  61. package/src/types/lib/Valid.d.ts.map +1 -0
  62. package/src/lib/Action.js +0 -283
  63. package/src/lib/ActionBuilder.js +0 -144
  64. package/src/lib/ActionRunner.js +0 -79
  65. package/src/lib/Hooks.js +0 -194
  66. package/src/lib/Piper.js +0 -155
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Utility class providing common helper functions for string manipulation,
3
+ * timing, hashing, and option parsing.
4
+ */
5
+ export default class Util {
6
+ /**
7
+ * Capitalizes the first letter of a string.
8
+ *
9
+ * @param {string} text - The text to capitalize
10
+ * @returns {string} Text with first letter capitalized
11
+ */
12
+ static capitalize(text: string): string;
13
+ /**
14
+ * Measure wall-clock time for an async function.
15
+ *
16
+ * @template T
17
+ * @param {() => Promise<T>} fn - Thunk returning a promise.
18
+ * @returns {Promise<{result: T, cost: number}>} Object containing result and elapsed ms (number, 1 decimal).
19
+ */
20
+ static time<T>(fn: () => Promise<T>): Promise<{
21
+ result: T;
22
+ cost: number;
23
+ }>;
24
+ /**
25
+ * Right-align a string inside a fixed width (left pad with spaces).
26
+ * If the string exceeds width it is returned unchanged.
27
+ *
28
+ * @param {string|number} text - Text to align.
29
+ * @param {number} width - Target field width (default 80).
30
+ * @returns {string} Padded string.
31
+ */
32
+ static rightAlignText(text: string | number, width?: number): string;
33
+ /**
34
+ * Centre-align a string inside a fixed width (pad with spaces on left).
35
+ * If the string exceeds width it is returned unchanged.
36
+ *
37
+ * @param {string|number} text - Text to align.
38
+ * @param {number} width - Target field width (default 80).
39
+ * @returns {string} Padded string with text centred.
40
+ */
41
+ static centreAlignText(text: string | number, width?: number): string;
42
+ /**
43
+ * Compute sha256 hash (hex) of the provided string.
44
+ *
45
+ * @param {string} s - Input string.
46
+ * @returns {string} 64-char hexadecimal digest.
47
+ */
48
+ static hashOf(s: string): string;
49
+ /**
50
+ * Extracts canonical option names from a Commander-style options object.
51
+ *
52
+ * Each key in the input object is a string containing one or more option
53
+ * forms, separated by commas (e.g. "-w, --watch"). This function splits each
54
+ * key, trims whitespace, and parses out the long option name (e.g. "watch")
55
+ * for each entry. If no long option ("--") is present, the short option (e.g.
56
+ * "v" from "-v") will be included in the result array. If both are present,
57
+ * the long option is preferred.
58
+ *
59
+ * Example:
60
+ * generateOptionNames({"-w, --watch": "desc", "-v": "desc"})
61
+ * → ["watch", "v"]
62
+ *
63
+ * Edge cases:
64
+ * - If a key contains only a short option ("-v"), that short name will be
65
+ * included in the result.
66
+ * - If multiple long options are present, only the first is used.
67
+ * - If the option string is malformed, may return undefined for that entry
68
+ * (filtered out).
69
+ *
70
+ * @param {object} object - Mapping of option strings to descriptions.
71
+ * @returns {Array<string>} Array of canonical option names (long preferred, short if no long present).
72
+ */
73
+ static generateOptionNames(object: object): Array<string>;
74
+ /**
75
+ * Asynchronously awaits all promises in parallel.
76
+ * Wrapper around Promise.all for consistency with other utility methods.
77
+ *
78
+ * @param {Array<Promise<unknown>>} promises - Array of promises to await
79
+ * @returns {Promise<Array<unknown>>} Results of all promises
80
+ */
81
+ static awaitAll(promises: Array<Promise<unknown>>): Promise<Array<unknown>>;
82
+ /**
83
+ * Settles all promises (both fulfilled and rejected) in parallel.
84
+ * Wrapper around Promise.allSettled for consistency with other utility methods.
85
+ *
86
+ * @param {Array<Promise<unknown>>} promises - Array of promises to settle
87
+ * @returns {Promise<Array<object>>} Results of all settled promises with status and value/reason
88
+ */
89
+ static settleAll(promises: Array<Promise<unknown>>): Promise<Array<object>>;
90
+ /**
91
+ * Checks if any result in the settled promise array is rejected.
92
+ *
93
+ * @param {Array<object>} result - Array of settled promise results.
94
+ * @returns {boolean} True if any result is rejected, false otherwise.
95
+ */
96
+ static anyRejected(result: Array<object>): boolean;
97
+ /**
98
+ * Filters and returns all rejected results from a settled promise array.
99
+ *
100
+ * @param {Array<object>} result - Array of settled promise results.
101
+ * @returns {Array<object>} Array of rejected results.
102
+ */
103
+ static settledAndRejected(result: Array<object>): Array<object>;
104
+ /**
105
+ * Extracts the rejection reasons from an array of rejected promise results.
106
+ *
107
+ * @param {Array<object>} rejected - Array of rejected results.
108
+ * @returns {Array<unknown>} Array of rejection reasons.
109
+ */
110
+ static rejectedReasons(rejected: Array<object>): Array<unknown>;
111
+ /**
112
+ * Throws a Sass error containing all rejection reasons from settled promises.
113
+ *
114
+ * @param {string} [_message] - Optional error message. Defaults to "GIGO"
115
+ * @param {Array<object>} rejected - Array of rejected results.
116
+ * @throws {Error} Throws a Sass error with rejection reasons.
117
+ */
118
+ static throwRejected(_message?: string, rejected: Array<object>): void;
119
+ /**
120
+ * Filters and returns all fulfilled results from a settled promise array.
121
+ *
122
+ * @param {Array<object>} result - Array of settled promise results.
123
+ * @returns {Array<object>} Array of fulfilled results.
124
+ */
125
+ static settledAndFulfilled(result: Array<object>): Array<object>;
126
+ /**
127
+ * Extracts the values from all fulfilled results in a settled promise array.
128
+ *
129
+ * @param {Array<object>} result - Array of settled promise results.
130
+ * @returns {Array<unknown>} Array of fulfilled values.
131
+ */
132
+ static fulfilledValues(result: Array<object>): Array<unknown>;
133
+ /**
134
+ * Returns the first promise to resolve or reject from an array of promises.
135
+ * Wrapper around Promise.race for consistency with other utility methods.
136
+ *
137
+ * @param {Array<Promise<unknown>>} promises - Array of promises to race
138
+ * @returns {Promise<unknown>} Result of the first settled promise
139
+ */
140
+ static race(promises: Array<Promise<unknown>>): Promise<unknown>;
141
+ /**
142
+ * Private method that performs the actual async emission logic.
143
+ * Handles listener execution, error aggregation, and result processing.
144
+ *
145
+ * @param {object} emitter - The emitter object (already validated)
146
+ * @param {string} event - The event name to emit
147
+ * @param {...unknown} args - Arguments to pass to event listeners
148
+ * @returns {Promise<void>} Resolves when all listeners have completed
149
+ */
150
+ static "__#private@#performAsyncEmit"(emitter: object, event: string, ...args: unknown[]): Promise<void>;
151
+ /**
152
+ * Emits an event asynchronously and waits for all listeners to complete.
153
+ * Unlike the standard EventEmitter.emit() which is synchronous, this method
154
+ * properly handles async event listeners by waiting for all of them to
155
+ * resolve or reject using Promise.allSettled().
156
+ *
157
+ * Uses strict instanceof checking to ensure the emitter is a genuine EventEmitter.
158
+ *
159
+ * @param {EventEmitter} emitter - The EventEmitter instance to emit on
160
+ * @param {string} event - The event name to emit
161
+ * @param {...unknown} args - Arguments to pass to event listeners
162
+ * @returns {Promise<void>} Resolves when all listeners have completed
163
+ */
164
+ static asyncEmit(emitter: EventEmitter, event: string, ...args: unknown[]): Promise<void>;
165
+ /**
166
+ * Emits an event asynchronously and waits for all listeners to complete.
167
+ * Like asyncEmit, but uses duck typing for more flexible emitter validation.
168
+ * Accepts any object that has the required EventEmitter-like methods.
169
+ *
170
+ * @param {object} emitter - Any object with EventEmitter-like interface
171
+ * @param {string} event - The event name to emit
172
+ * @param {...unknown} args - Arguments to pass to event listeners
173
+ * @returns {Promise<void>} Resolves when all listeners have completed
174
+ */
175
+ static asyncEmitAnon(emitter: object, event: string, ...args: unknown[]): Promise<void>;
176
+ /**
177
+ * Determine the Levenshtein distance between two string values
178
+ *
179
+ * @param {string} a The first value for comparison.
180
+ * @param {string} b The second value for comparison.
181
+ * @returns {number} The Levenshtein distance
182
+ */
183
+ static levenshteinDistance(a: string, b: string): number;
184
+ /**
185
+ * Determine the closest match between a string and allowed values
186
+ * from the Levenshtein distance.
187
+ *
188
+ * @param {string} input The input string to resolve
189
+ * @param {Array<string>} allowedValues The values which are permitted
190
+ * @param {number} [threshold] Max edit distance for a "close match"
191
+ * @returns {string} Suggested, probable match.
192
+ */
193
+ static findClosestMatch(input: string, allowedValues: Array<string>, threshold?: number): string;
194
+ static regexify(input: any, trim?: boolean, flags?: any[]): RegExp;
195
+ }
196
+ import { EventEmitter } from "node:events";
197
+ //# sourceMappingURL=Util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Util.d.ts","sourceRoot":"","sources":["../../lib/Util.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH;IACE;;;;;OAKG;IACH,wBAHW,MAAM,GACJ,MAAM,CAYlB;IAED;;;;;;OAMG;IACH,YAJa,CAAC,MACH,MAAM,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC;QAAC,MAAM,EAAE,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAC,CAQ9C;IAED;;;;;;;OAOG;IACH,4BAJW,MAAM,GAAC,MAAM,UACb,MAAM,GACJ,MAAM,CAWlB;IAED;;;;;;;OAOG;IACH,6BAJW,MAAM,GAAC,MAAM,UACb,MAAM,GACJ,MAAM,CAalB;IAED;;;;;OAKG;IACH,iBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,mCAHW,MAAM,GACJ,KAAK,CAAC,MAAM,CAAC,CAazB;IAED;;;;;;OAMG;IACH,0BAHW,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GACrB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAInC;IAED;;;;;;OAMG;IACH,2BAHW,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GACrB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAIlC;IAED;;;;;OAKG;IACH,2BAHW,KAAK,CAAC,MAAM,CAAC,GACX,OAAO,CAInB;IAED;;;;;OAKG;IACH,kCAHW,KAAK,CAAC,MAAM,CAAC,GACX,KAAK,CAAC,MAAM,CAAC,CAIzB;IAED;;;;;OAKG;IACH,iCAHW,KAAK,CAAC,MAAM,CAAC,GACX,KAAK,CAAC,OAAO,CAAC,CAI1B;IAED;;;;;;OAMG;IACH,gCAJW,MAAM,YACN,KAAK,CAAC,MAAM,CAAC,QAKvB;IAED;;;;;OAKG;IACH,mCAHW,KAAK,CAAC,MAAM,CAAC,GACX,KAAK,CAAC,MAAM,CAAC,CAIzB;IAED;;;;;OAKG;IACH,+BAHW,KAAK,CAAC,MAAM,CAAC,GACX,KAAK,CAAC,OAAO,CAAC,CAI1B;IAED;;;;;;OAMG;IACH,sBAHW,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GACrB,OAAO,CAAC,OAAO,CAAC,CAI5B;IAED;;;;;;;;OAQG;IACH,+CALW,MAAM,SACN,MAAM,WACH,OAAO,EAAA,GACR,OAAO,CAAC,IAAI,CAAC,CAmBzB;IAED;;;;;;;;;;;;OAYG;IACH,0BALW,YAAY,SACZ,MAAM,WACH,OAAO,EAAA,GACR,OAAO,CAAC,IAAI,CAAC,CAqBzB;IAED;;;;;;;;;OASG;IACH,8BALW,MAAM,SACN,MAAM,WACH,OAAO,EAAA,GACR,OAAO,CAAC,IAAI,CAAC,CAyBzB;IAED;;;;;;OAMG;IACH,8BAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAsBlB;IAED;;;;;;;;OAQG;IACH,+BALW,MAAM,iBACN,KAAK,CAAC,MAAM,CAAC,cACb,MAAM,GACJ,MAAM,CAwBlB;IAED,mEAiBC;CACF;6BAjZ0B,aAAa"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Validation utility class providing type checking and assertion methods.
3
+ */
4
+ export default class Valid {
5
+ /**
6
+ * Validates a value against a type. Uses Data.isType.
7
+ *
8
+ * @param {unknown} value - The value to validate
9
+ * @param {string} type - The expected type in the form of "object", "object[]", "object|object[]"
10
+ * @param {object} [options] - Additional options for validation.
11
+ */
12
+ static type(value: unknown, type: string, options?: object): void;
13
+ /**
14
+ * Asserts a condition
15
+ *
16
+ * @param {boolean} condition - The condition to assert
17
+ * @param {string} message - The message to display if the condition is not
18
+ * met
19
+ * @param {number} [arg] - The argument to display if the condition is not
20
+ * met (optional)
21
+ */
22
+ static assert(condition: boolean, message: string, arg?: number): void;
23
+ static "__#private@#restrictedProto": string[];
24
+ /**
25
+ * Protects against prototype pollution by checking keys for dangerous property names.
26
+ * Throws if any restricted prototype properties are found in the keys array.
27
+ *
28
+ * @param {Array<string>} keys - Array of property keys to validate
29
+ * @throws {Sass} If any key matches restricted prototype properties (__proto__, constructor, prototype)
30
+ */
31
+ static prototypePollutionProtection(keys: Array<string>): void;
32
+ }
33
+ //# sourceMappingURL=Valid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Valid.d.ts","sourceRoot":"","sources":["../../lib/Valid.js"],"names":[],"mappings":"AAaA;;GAEG;AACH;IACE;;;;;;OAMG;IACH,mBAJW,OAAO,QACP,MAAM,YACN,MAAM,QAQhB;IAED;;;;;;;;OAQG;IACH,yBANW,OAAO,WACP,MAAM,QAEN,MAAM,QAmBhB;IAED,+CAAmE;IAEnE;;;;;;OAMG;IACH,0CAHW,KAAK,CAAC,MAAM,CAAC,QAYvB;CACF"}
package/src/lib/Action.js DELETED
@@ -1,283 +0,0 @@
1
- import ActionBuilder from "./ActionBuilder.js"
2
- import ActionRunner from "./ActionRunner.js"
3
- import Data from "./Data.js"
4
- import FileObject from "./FileObject.js"
5
- import Hooks from "./Hooks.js"
6
- import Sass from "./Sass.js"
7
- import Terms from "./Terms.js"
8
-
9
- /**
10
- * Generic base class for managing actions with lifecycle hooks.
11
- * Provides common functionality for action setup, execution, and cleanup.
12
- * Designed to be extended by specific implementations.
13
- */
14
- export default class Action {
15
- #action = null
16
- #hooks = null
17
- #file = null
18
- #variables = null
19
- #runner = null
20
- #id = null
21
- #debug
22
-
23
- /**
24
- * Creates a new BaseActionManager instance.
25
- *
26
- * @param {object} config - Configuration object
27
- * @param {object} config.actionDefinition - Action definition containing action class and file info
28
- * @param {object} [config.variables] - Variables to pass to action during setup
29
- * @param {import('../types.js').DebugFunction} config.debug - The logger's debug function
30
- */
31
- constructor({actionDefinition, variables, debug}) {
32
- this.#id = Symbol(performance.now())
33
- this.#variables = variables || {}
34
- this.#debug = debug
35
-
36
- const {action,file} = actionDefinition
37
- this.#action = action
38
- this.#file = file
39
- }
40
- /**
41
- * Gets the unique identifier for this action manager instance.
42
- *
43
- * @returns {symbol} Unique symbol identifier
44
- */
45
- get id() {
46
- return this.#id
47
- }
48
-
49
- /**
50
- * Gets the action class constructor.
51
- *
52
- * @returns {new () => object} Action class constructor
53
- */
54
- get action() {
55
- return this.#action
56
- }
57
-
58
- /**
59
- * Gets the current hook manager instance.
60
- *
61
- * @returns {Hooks|null} Hook manager instance or null if not set
62
- */
63
- get hooks() {
64
- return this.#hooks
65
- }
66
-
67
- /**
68
- * Sets the hook manager and attaches hooks to the action.
69
- *
70
- * @param {Hooks} hooks - Hook manager instance with hooks and on method.
71
- * @returns {Promise<this>} Promise of this instance.
72
- * @throws {Sass} If hook manager is already set.
73
- */
74
- setHooks(hooks) {
75
- if(this.#hooks)
76
- throw Sass.new("Hook manager already set")
77
-
78
- this.#hooks = hooks
79
-
80
- return this
81
- }
82
-
83
- // async callHook(kind, activity, action, context) {
84
- // const hooks = this.#hooks
85
-
86
- // }
87
-
88
- /**
89
- * Gets the action metadata.
90
- *
91
- * @returns {object|undefined} Action metadata object
92
- */
93
- get meta() {
94
- return this.#action?.meta
95
- }
96
-
97
- /**
98
- * Gets the variables passed to the action.
99
- *
100
- * @returns {object} Variables object
101
- */
102
- get variables() {
103
- return this.#variables
104
- }
105
-
106
- /**
107
- * Gets the action runner instance.
108
- *
109
- * @returns {ActionRunner?} ActionRunner instance or null if not set up
110
- */
111
- get runner() {
112
- return this.#runner
113
- }
114
-
115
- /**
116
- * Gets the file information object.
117
- *
118
- * @returns {FileObject?} File information object
119
- */
120
- get file() {
121
- return this.#file
122
- }
123
-
124
- /**
125
- * Setup the action by creating and configuring the runner.
126
- * This is the main public method to initialize the action for use.
127
- *
128
- * @returns {Promise<this>} Promise of this instance.
129
- * @throws {Sass} If action setup fails
130
- */
131
- async setupAction() {
132
- this.#debug("Setting up action for %o on %o", 2, this.#action.meta?.kind, this.id)
133
-
134
- await this.#setupHooks()
135
- await this.#setupAction()
136
-
137
- return this
138
- }
139
-
140
- /**
141
- * Setup the action instance and create the runner.
142
- * Creates a new action instance, calls its setup method with an
143
- * ActionBuilder, and creates an ActionRunner from the result.
144
- *
145
- * Can be overridden in subclasses to customize action setup.
146
- *
147
- * @returns {Promise<void>}
148
- * @throws {Sass} If action setup method is not a function
149
- * @protected
150
- */
151
- async #setupAction() {
152
- const actionInstance = new this.#action()
153
- const setup = actionInstance?.setup
154
-
155
- // Setup is required for actions.
156
- if(Data.typeOf(setup) === "Function") {
157
- const builder = new ActionBuilder(actionInstance)
158
- const configuredBuilder = setup(builder)
159
- const buildResult = configuredBuilder.build()
160
- const runner = new ActionRunner({
161
- action: buildResult.action,
162
- build: buildResult.build
163
- }, this.#hooks)
164
-
165
- this.#runner = runner
166
- } else {
167
- throw Sass.new("Action setup must be a function.")
168
- }
169
- }
170
-
171
- /**
172
- * Run the action with the provided input.
173
- * The action must be set up via setupAction() before calling this method.
174
- *
175
- * @param {unknown} context - Input data to pass to the action runner
176
- * @returns {Promise<unknown>} Result from the action execution
177
- * @throws {Sass} If action is not set up
178
- */
179
- async runAction(context) {
180
- if(!this.#runner)
181
- throw Sass.new("Action not set up. Call setupAction() first.")
182
-
183
- return await this.#runner.run(context)
184
- }
185
-
186
- /**
187
- * Cleanup the action and hooks.
188
- * This should be called when the action is no longer needed to free
189
- * resources.
190
- *
191
- * Calls cleanupHooks() and cleanupActionInstance() which can be overridden.
192
- *
193
- * @returns {Promise<this>} Promise of this instance.
194
- */
195
- async cleanupAction() {
196
- this.#debug("Cleaning up action for %o on %o", 2, this.#action.meta?.kind, this.id)
197
-
198
- await this.#cleanupHooks()
199
- await this.#cleanupAction()
200
-
201
- return this
202
- }
203
-
204
- /**
205
- * Setup hooks if hook manager is present.
206
- * Calls the hook manager's setup function with action context.
207
- * Override in subclasses to customize hook setup.
208
- *
209
- * @returns {Promise<void>}
210
- * @throws {Sass} If hook setup is not a function
211
- * @private
212
- */
213
- async #setupHooks() {
214
- const setup = this.#hooks?.setup
215
- const type = Data.typeOf(setup)
216
-
217
- // No hooks attached.
218
- if(type === "Null" || type === "Undefined")
219
- return
220
-
221
- if(type !== "Function")
222
- throw Sass.new("Hook setup must be a function.")
223
-
224
- await setup.call(
225
- this.hooks.hooks, {
226
- action: this.#action,
227
- variables: this.#variables,
228
- }
229
- )
230
- }
231
-
232
- /**
233
- * Cleanup hooks if hook manager is present.
234
- * Calls the hook manager's cleanup function.
235
- * Override in subclasses to customize hook cleanup.
236
- *
237
- * @returns {Promise<void>}
238
- * @protected
239
- */
240
- async #cleanupHooks() {
241
- const cleanup = this.hooks?.cleanup
242
-
243
- if(!cleanup)
244
- return
245
-
246
- await cleanup.call(this.hooks.hooks)
247
- }
248
-
249
- /**
250
- * Cleanup the action instance.
251
- * Calls the action's cleanup method if it exists.
252
- * Override in subclasses to add custom cleanup logic.
253
- *
254
- * @returns {Promise<void>}
255
- * @protected
256
- */
257
- async #cleanupAction() {
258
- const cleanup = this.#action?.cleanup
259
-
260
- if(!cleanup)
261
- return
262
-
263
- await cleanup.call(this.#action)
264
- }
265
-
266
- /**
267
- * Returns a string representation of this action manager.
268
- *
269
- * @returns {string} String representation with module and action info
270
- */
271
- toString() {
272
- return `${this.#file?.module || "UNDEFINED"} (${this.meta?.action || "UNDEFINED"})`
273
- }
274
-
275
- /**
276
- * Get contract/terms for this action (override in subclasses)
277
- *
278
- * @returns {Terms?} Contract terms or null if not implemented
279
- */
280
- get terms() {
281
- return null
282
- }
283
- }
@@ -1,144 +0,0 @@
1
- import Valid from "./Valid.js"
2
-
3
- /** @typedef {import("./ActionRunner.js").default} ActionRunner */
4
-
5
- /**
6
- * Activity bit flags recognised by {@link ActionBuilder#act}. The flag decides
7
- * how results are accumulated for each activity.
8
- *
9
- * @readonly
10
- * @enum {number}
11
- */
12
- export const ACTIVITY = Object.freeze({
13
- ONCE: 1<<1,
14
- MANY: 1<<2,
15
- PARALLEL: 1<<3,
16
- })
17
-
18
- /**
19
- * Fluent builder for describing how an action should process the context that
20
- * flows through the {@link ActionRunner}. Consumers register named activities,
21
- * optional hook pairs, and nested parallel pipelines before handing the
22
- * builder back to the runner for execution.
23
- *
24
- * Typical usage:
25
- *
26
- * ```js
27
- * const pipeline = new ActionBuilder(myAction)
28
- * .act("prepare", ACTIVITY.ONCE, ctx => ctx.initialise())
29
- * .parallel(parallel => parallel
30
- * .act("step", ACTIVITY.MANY, ctx => ctx.consume())
31
- * )
32
- * .act("finalise", ACTIVITY.ONCE, ctx => ctx.complete())
33
- * .build()
34
- * ```
35
- *
36
- * @class ActionBuilder
37
- */
38
- export default class ActionBuilder {
39
- #action = null
40
- #activities = new Map([])
41
-
42
- /**
43
- * Creates a new ActionBuilder instance with the provided action callback.
44
- *
45
- * @param {(ctx: object) => void} action Base action invoked by the runner when a block
46
- * satisfies the configured structure.
47
- */
48
- constructor(action) {
49
- this.#action = action
50
- }
51
-
52
- /**
53
- * Returns the underlying action that will receive the extracted context.
54
- *
55
- * @returns {(ctx: object) => void} The action callback function that processes the extracted context.
56
- */
57
- get action() {
58
- return this.#action
59
- }
60
-
61
- /**
62
- * Returns the registered activities keyed by their name.
63
- *
64
- * @returns {Map<string | symbol, {op: (context: object) => unknown, kind: number, hooks: {before: ((context: object) => void)|null, after: ((context: object) => void)|null}}>} Map of registered activities and their metadata.
65
- */
66
- get activities() {
67
- return this.#activities
68
- }
69
-
70
- /**
71
- * Registers a named activity that will run for each matching block.
72
- *
73
- * @param {string} name Unique activity identifier.
74
- * @param {number} kind Activity accumulation strategy (see {@link ACTIVITY}).
75
- * @param {(context: object) => unknown} op Work function executed with the runner context.
76
- * @param {{before?: (context: object) => void, after?: (context: object) => void}} [hooks] Optional hooks to run before or after the activity operation.
77
- * @returns {ActionBuilder} Builder instance for chaining.
78
- */
79
- act(name, kind, op, hooks={}) {
80
- this.#validActivityKind(kind)
81
- this.#dupeActivityCheck(name)
82
-
83
- hooks = this.#normalizeHooks(hooks)
84
-
85
- this.#activities.set(name, {op, kind, hooks})
86
-
87
- return this
88
- }
89
-
90
- #normalizeHooks({before=null, after=null}) {
91
- return {before, after}
92
- }
93
-
94
- /**
95
- * Defines a nested pipeline that will run with the {@link ACTIVITY} flag PARALLEL.
96
- *
97
- * The callback receives a fresh {@link ActionBuilder} scoped to the current
98
- * action. The callback must return the configured builder so the runner can
99
- * execute the nested pipeline.
100
- *
101
- * @param {(builder: ActionBuilder) => ActionBuilder} func Callback configuring a nested builder.
102
- * @returns {ActionBuilder} Builder instance for chaining.
103
- */
104
- parallel(func) {
105
- const activityName = Symbol(performance.now())
106
-
107
- this.#activities.set(activityName, {
108
- op: func.call(this.action, new ActionBuilder(this.action)),
109
- kind: ACTIVITY.PARALLEL
110
- })
111
-
112
- return this
113
- }
114
-
115
- #validActivityKind(kind) {
116
- Valid.assert(
117
- Object.values(ACTIVITY).includes(kind),
118
- "Invalid activity kind."
119
- )
120
- }
121
-
122
- /**
123
- * Validates that an activity name has not been reused.
124
- *
125
- * @private
126
- * @param {string|symbol} name Activity identifier.
127
- */
128
- #dupeActivityCheck(name) {
129
- Valid.assert(
130
- !this.#activities.has(name),
131
- `Activity '${String(name)}' has already been registered.`
132
- )
133
- }
134
-
135
- /**
136
- * Finalises the builder and returns a payload that can be consumed by the
137
- * runner.
138
- *
139
- * @returns {{action: (context: object) => unknown, build: ActionBuilder}} Payload consumed by the {@link ActionRunner} constructor.
140
- */
141
- build() {
142
- return {action: this.#action, build: this}
143
- }
144
- }