@gesslar/toolkit 0.3.0 → 0.5.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 +3 -2
- package/src/index.js +3 -0
- package/src/lib/Action.js +283 -0
- package/src/lib/ActionRunner.js +21 -51
- package/src/lib/Contract.js +257 -0
- package/src/lib/Data.js +1 -1
- package/src/lib/DirectoryObject.js +94 -18
- package/src/lib/FileObject.js +39 -19
- package/src/lib/Glog.js +3 -2
- package/src/lib/Hooks.js +194 -0
- package/src/lib/Piper.js +74 -100
- package/src/lib/Schemer.js +89 -0
- package/src/lib/Terms.js +74 -0
- package/src/types/Contract.d.ts +162 -0
- package/src/types/DirectoryObject.d.ts +65 -2
- package/src/types/FileObject.d.ts +38 -2
- package/src/types/Schemer.d.ts +179 -0
- package/src/types/Terms.d.ts +145 -0
- package/src/types/index.d.ts +3 -0
- package/src/lib/BaseActionManager.js +0 -246
- package/src/lib/BaseHookManager.js +0 -206
|
@@ -321,10 +321,46 @@ export default class FileObject extends FS {
|
|
|
321
321
|
/** Read the content of a file */
|
|
322
322
|
read(encoding?: string): Promise<string>
|
|
323
323
|
|
|
324
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* Write content to a file asynchronously.
|
|
326
|
+
* Validates that the parent directory exists before writing.
|
|
327
|
+
*
|
|
328
|
+
* @param content - The content to write
|
|
329
|
+
* @param encoding - The encoding in which to write (default: "utf8")
|
|
330
|
+
* @throws {Sass} If the file path is invalid or the parent directory doesn't exist
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```typescript
|
|
334
|
+
* const file = new FileObject('./output/data.json')
|
|
335
|
+
* await file.write(JSON.stringify({key: 'value'}))
|
|
336
|
+
*
|
|
337
|
+
* // With custom encoding
|
|
338
|
+
* await file.write('content', 'utf16le')
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
325
341
|
write(content: string, encoding?: string): Promise<void>
|
|
326
342
|
|
|
327
|
-
/**
|
|
343
|
+
/**
|
|
344
|
+
* Load and parse data from JSON5 or YAML file.
|
|
345
|
+
* Attempts to parse content as JSON5 first, then falls back to YAML if type is "any".
|
|
346
|
+
*
|
|
347
|
+
* @param type - The expected data format: "json", "json5", "yaml", or "any" (default: "any")
|
|
348
|
+
* @param encoding - The file encoding (default: "utf8")
|
|
349
|
+
* @returns The parsed data object
|
|
350
|
+
* @throws {Sass} If the content cannot be parsed or type is unsupported
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* // Load JSON5 config
|
|
355
|
+
* const config = await configFile.loadData('json5')
|
|
356
|
+
*
|
|
357
|
+
* // Auto-detect format (tries JSON5, then YAML)
|
|
358
|
+
* const data = await dataFile.loadData('any')
|
|
359
|
+
*
|
|
360
|
+
* // Load YAML explicitly
|
|
361
|
+
* const yaml = await yamlFile.loadData('yaml')
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
328
364
|
loadData(type?: 'json' | 'json5' | 'yaml' | 'any', encoding?: string): Promise<unknown>
|
|
329
365
|
|
|
330
366
|
/**
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Implementation: ../lib/Schemer.js
|
|
2
|
+
|
|
3
|
+
import type { ValidateFunction, ErrorObject } from 'ajv'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Schemer provides utilities for compiling and validating JSON schemas using AJV.
|
|
7
|
+
*
|
|
8
|
+
* This class serves as a convenient wrapper around AJV (Another JSON Schema Validator)
|
|
9
|
+
* with toolkit-specific enhancements for error reporting and schema compilation.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Create validator from schema object
|
|
14
|
+
* const validator = await Schemer.from({
|
|
15
|
+
* type: "object",
|
|
16
|
+
* properties: {
|
|
17
|
+
* name: { type: "string" },
|
|
18
|
+
* age: { type: "number", minimum: 0 }
|
|
19
|
+
* },
|
|
20
|
+
* required: ["name"]
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* // Validate data
|
|
24
|
+
* const isValid = validator({ name: "John", age: 30 })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Create validator from file
|
|
30
|
+
* const file = new FileObject("schema.json")
|
|
31
|
+
* const validator = await Schemer.fromFile(file)
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Get raw AJV validator and format errors
|
|
37
|
+
* const validate = Schemer.getValidator(schema)
|
|
38
|
+
* const isValid = validate(data)
|
|
39
|
+
* if (!isValid) {
|
|
40
|
+
* const errorReport = Schemer.reportValidationErrors(validate.errors)
|
|
41
|
+
* console.error("Validation failed:", errorReport)
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare class Schemer {
|
|
46
|
+
/**
|
|
47
|
+
* Creates an AJV validator function from a schema file
|
|
48
|
+
*
|
|
49
|
+
* @param file - FileObject pointing to a JSON/YAML schema file
|
|
50
|
+
* @param options - AJV configuration options
|
|
51
|
+
* @returns Promise resolving to AJV validator function
|
|
52
|
+
*
|
|
53
|
+
* @throws {Sass} If file cannot be loaded or schema is invalid
|
|
54
|
+
* @throws {Sass} If file is not a FileObject or options are invalid
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const file = new FileObject("user-schema.json")
|
|
59
|
+
* const validator = await Schemer.fromFile(file, {
|
|
60
|
+
* allErrors: true,
|
|
61
|
+
* verbose: true
|
|
62
|
+
* })
|
|
63
|
+
*
|
|
64
|
+
* const isValid = validator({ name: "John", age: 30 })
|
|
65
|
+
* if (!isValid) {
|
|
66
|
+
* console.log("Errors:", validator.errors)
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
static fromFile(
|
|
71
|
+
file: import('./FileObject.js').default,
|
|
72
|
+
options?: object
|
|
73
|
+
): Promise<ValidateFunction>
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates an AJV validator function from a schema object
|
|
77
|
+
*
|
|
78
|
+
* @param schemaData - JSON schema object to compile
|
|
79
|
+
* @param options - AJV configuration options
|
|
80
|
+
* @returns Promise resolving to AJV validator function
|
|
81
|
+
*
|
|
82
|
+
* @throws {Sass} If schema data or options are not plain objects
|
|
83
|
+
* @throws {Sass} If schema compilation fails
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const validator = await Schemer.from({
|
|
88
|
+
* type: "object",
|
|
89
|
+
* properties: {
|
|
90
|
+
* id: { type: "string", format: "uuid" },
|
|
91
|
+
* email: { type: "string", format: "email" }
|
|
92
|
+
* },
|
|
93
|
+
* required: ["id", "email"]
|
|
94
|
+
* }, {
|
|
95
|
+
* formats: true,
|
|
96
|
+
* allErrors: true
|
|
97
|
+
* })
|
|
98
|
+
*
|
|
99
|
+
* const isValid = validator({ id: "123", email: "test@example.com" })
|
|
100
|
+
* if (!isValid) {
|
|
101
|
+
* console.log("Errors:", validator.errors)
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
static from(schemaData?: object, options?: object): Promise<ValidateFunction>
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a raw AJV validator function from a schema object
|
|
109
|
+
*
|
|
110
|
+
* @param schema - The JSON schema to compile
|
|
111
|
+
* @param options - AJV configuration options (defaults to {allErrors: true, verbose: true})
|
|
112
|
+
* @returns AJV validator function with .errors property when validation fails
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const validate = Schemer.getValidator({
|
|
117
|
+
* type: "string",
|
|
118
|
+
* minLength: 1,
|
|
119
|
+
* maxLength: 100
|
|
120
|
+
* })
|
|
121
|
+
*
|
|
122
|
+
* const isValid = validate("Hello World")
|
|
123
|
+
* if (!isValid) {
|
|
124
|
+
* console.log("Errors:", validate.errors)
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // Custom AJV options
|
|
131
|
+
* const validate = Schemer.getValidator(schema, {
|
|
132
|
+
* allErrors: false,
|
|
133
|
+
* verbose: false,
|
|
134
|
+
* strict: true
|
|
135
|
+
* })
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
static getValidator(
|
|
139
|
+
schema: object,
|
|
140
|
+
options?: { allErrors?: boolean; verbose?: boolean; [key: string]: unknown }
|
|
141
|
+
): ValidateFunction
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Formats AJV validation errors into a human-readable report
|
|
145
|
+
*
|
|
146
|
+
* @param errors - Array of AJV error objects from failed validation
|
|
147
|
+
* @returns Formatted error message with helpful details and suggestions
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const validate = Schemer.getValidator(schema)
|
|
152
|
+
* const isValid = validate(data)
|
|
153
|
+
*
|
|
154
|
+
* if (!isValid) {
|
|
155
|
+
* const report = Schemer.reportValidationErrors(validate.errors)
|
|
156
|
+
* console.error("Validation failed:")
|
|
157
|
+
* console.error(report)
|
|
158
|
+
* // Output:
|
|
159
|
+
* // - "(root)" must be object
|
|
160
|
+
* // ➜ Expected type: object
|
|
161
|
+
* // ➜ Received value: "string"
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* // The error report includes helpful details:
|
|
168
|
+
* // - Property paths and error descriptions
|
|
169
|
+
* // - Expected vs actual types
|
|
170
|
+
* // - Missing required fields
|
|
171
|
+
* // - Pattern matching failures
|
|
172
|
+
* // - Closest matches for enum values
|
|
173
|
+
* // - Unexpected additional properties
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
static reportValidationErrors(errors: ErrorObject[] | null | undefined): string
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default Schemer
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Implementation: ../lib/Terms.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Terms represents an interface definition - what an action promises to provide or accept.
|
|
5
|
+
* It's just the specification, not the negotiation. Contract handles the negotiation.
|
|
6
|
+
*
|
|
7
|
+
* Terms can be created from objects, strings (YAML/JSON), or file references.
|
|
8
|
+
* File references use the format "ref://path/to/file" for loading external definitions.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Create terms from object definition
|
|
13
|
+
* const terms = new Terms({
|
|
14
|
+
* provides: {
|
|
15
|
+
* type: "object",
|
|
16
|
+
* properties: {
|
|
17
|
+
* userId: { type: "string" },
|
|
18
|
+
* userName: { type: "string" }
|
|
19
|
+
* },
|
|
20
|
+
* required: ["userId"]
|
|
21
|
+
* }
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // Parse terms from YAML string
|
|
28
|
+
* const yamlData = `
|
|
29
|
+
* accepts:
|
|
30
|
+
* type: object
|
|
31
|
+
* properties:
|
|
32
|
+
* input:
|
|
33
|
+
* type: string
|
|
34
|
+
* minLength: 1
|
|
35
|
+
* `
|
|
36
|
+
* const parsedTerms = await Terms.parse(yamlData)
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Parse terms from file reference
|
|
42
|
+
* const directory = new DirectoryObject("/path/to/schemas")
|
|
43
|
+
* const parsedTerms = await Terms.parse("ref://user-schema.json", directory)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare class Terms {
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new Terms instance with the given definition
|
|
49
|
+
*
|
|
50
|
+
* @param definition - The terms definition object describing what is provided or accepted
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const terms = new Terms({
|
|
55
|
+
* provides: {
|
|
56
|
+
* type: "object",
|
|
57
|
+
* properties: {
|
|
58
|
+
* data: { type: "array", items: { type: "string" } },
|
|
59
|
+
* metadata: {
|
|
60
|
+
* type: "object",
|
|
61
|
+
* properties: {
|
|
62
|
+
* timestamp: { type: "string", format: "date-time" }
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
constructor(definition: object)
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parses terms data from various sources, handling file references
|
|
74
|
+
*
|
|
75
|
+
* @param termsData - Terms data as string (YAML/JSON/file reference) or object
|
|
76
|
+
* @param directoryObject - Directory context for resolving file references (required for ref:// URLs)
|
|
77
|
+
* @returns Promise resolving to parsed terms data object
|
|
78
|
+
*
|
|
79
|
+
* @throws {Sass} If termsData is not a string or object
|
|
80
|
+
* @throws {Sass} If string data cannot be parsed as YAML or JSON
|
|
81
|
+
* @throws {Sass} If file reference cannot be loaded (missing directory or file not found)
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Parse from YAML string
|
|
86
|
+
* const yamlTerms = await Terms.parse(`
|
|
87
|
+
* provides:
|
|
88
|
+
* type: string
|
|
89
|
+
* pattern: "^[A-Z][a-z]+"
|
|
90
|
+
* `)
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // Parse from JSON string
|
|
96
|
+
* const jsonTerms = await Terms.parse(`{
|
|
97
|
+
* "accepts": {
|
|
98
|
+
* "type": "number",
|
|
99
|
+
* "minimum": 0,
|
|
100
|
+
* "maximum": 100
|
|
101
|
+
* }
|
|
102
|
+
* }`)
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // Parse from file reference
|
|
108
|
+
* const directory = new DirectoryObject("./schemas")
|
|
109
|
+
* const fileTerms = await Terms.parse("ref://api-contract.yaml", directory)
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* // Parse from object (returns as-is)
|
|
115
|
+
* const objectTerms = await Terms.parse({
|
|
116
|
+
* provides: { type: "boolean" }
|
|
117
|
+
* })
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
static parse(
|
|
121
|
+
termsData: string | object,
|
|
122
|
+
directoryObject?: import('./DirectoryObject.js').default
|
|
123
|
+
): Promise<object>
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get the terms definition object
|
|
127
|
+
*
|
|
128
|
+
* @returns The complete terms definition as provided to the constructor
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const terms = new Terms({
|
|
133
|
+
* accepts: { type: "string" },
|
|
134
|
+
* provides: { type: "number" }
|
|
135
|
+
* })
|
|
136
|
+
*
|
|
137
|
+
* const definition = terms.definition
|
|
138
|
+
* console.log(definition.accepts) // { type: "string" }
|
|
139
|
+
* console.log(definition.provides) // { type: "number" }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
get definition(): object
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default Terms
|
package/src/types/index.d.ts
CHANGED
|
@@ -7,11 +7,14 @@ export { default as FS } from './FS.js'
|
|
|
7
7
|
// Utility classes
|
|
8
8
|
export { default as Cache } from './Cache.js'
|
|
9
9
|
export { default as Collection } from './Collection.js'
|
|
10
|
+
export { default as Contract } from './Contract.js'
|
|
10
11
|
export { default as Data } from './Data.js'
|
|
11
12
|
export { default as Glog } from './Glog.js'
|
|
12
13
|
export { default as Sass } from './Sass.js'
|
|
14
|
+
export { default as Schemer } from './Schemer.js'
|
|
13
15
|
export { default as Tantrum } from './Tantrum.js'
|
|
14
16
|
export { default as Term } from './Term.js'
|
|
17
|
+
export { default as Terms } from './Terms.js'
|
|
15
18
|
export { default as Type } from './Type.js'
|
|
16
19
|
export { default as Util } from './Util.js'
|
|
17
20
|
export { default as Valid } from './Valid.js'
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import Data from "./Data.js"
|
|
2
|
-
import Sass from "./Sass.js"
|
|
3
|
-
import ActionBuilder from "./ActionBuilder.js"
|
|
4
|
-
import ActionRunner from "./ActionRunner.js"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generic base class for managing actions with lifecycle hooks.
|
|
8
|
-
* Provides common functionality for action setup, execution, and cleanup.
|
|
9
|
-
* Designed to be extended by specific implementations.
|
|
10
|
-
*/
|
|
11
|
-
export default class BaseActionManager {
|
|
12
|
-
#action = null
|
|
13
|
-
#hookManager = null
|
|
14
|
-
#contract = null
|
|
15
|
-
#log = null
|
|
16
|
-
#debug = null
|
|
17
|
-
#file = null
|
|
18
|
-
#variables = null
|
|
19
|
-
#runner = null
|
|
20
|
-
#id = null
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {object} config - Configuration object
|
|
24
|
-
* @param {object} config.actionDefinition - Action definition with action, file, and contract
|
|
25
|
-
* @param {object} config.logger - Logger instance
|
|
26
|
-
* @param {object} [config.variables] - Variables to pass to action
|
|
27
|
-
*/
|
|
28
|
-
constructor({actionDefinition, logger, variables}) {
|
|
29
|
-
this.#id = Symbol(performance.now())
|
|
30
|
-
this.#log = logger
|
|
31
|
-
this.#debug = this.#log.newDebug()
|
|
32
|
-
this.#variables = variables || {}
|
|
33
|
-
|
|
34
|
-
this.#initialize(actionDefinition)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get id() {
|
|
38
|
-
return this.#id
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get action() {
|
|
42
|
-
return this.#action
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get hookManager() {
|
|
46
|
-
return this.#hookManager
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
set hookManager(hookManager) {
|
|
50
|
-
if (this.hookManager)
|
|
51
|
-
throw new Error("Hook manager already set")
|
|
52
|
-
|
|
53
|
-
this.#hookManager = hookManager
|
|
54
|
-
this.#attachHooksToAction(hookManager)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get contract() {
|
|
58
|
-
return this.#contract
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get meta() {
|
|
62
|
-
return this.#action?.meta
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get log() {
|
|
66
|
-
return this.#log
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
get variables() {
|
|
70
|
-
return this.#variables
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get runner() {
|
|
74
|
-
return this.#runner
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get file() {
|
|
78
|
-
return this.#file
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Initialize the action manager with the provided definition.
|
|
83
|
-
* Override in subclasses to add specific validation or setup.
|
|
84
|
-
*
|
|
85
|
-
* @param {object} actionDefinition - Action definition
|
|
86
|
-
* @protected
|
|
87
|
-
*/
|
|
88
|
-
#initialize(actionDefinition) {
|
|
89
|
-
const debug = this.#debug
|
|
90
|
-
|
|
91
|
-
debug("Setting up action", 2)
|
|
92
|
-
|
|
93
|
-
const {action, file, contract} = actionDefinition
|
|
94
|
-
|
|
95
|
-
if (!action)
|
|
96
|
-
throw new Error("Action is required")
|
|
97
|
-
|
|
98
|
-
if (!contract)
|
|
99
|
-
throw new Error("Contract is required")
|
|
100
|
-
|
|
101
|
-
this.#action = action
|
|
102
|
-
this.#contract = contract
|
|
103
|
-
this.#file = file
|
|
104
|
-
|
|
105
|
-
debug("Action initialization complete", 2)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Attach hooks to the action instance.
|
|
110
|
-
* Override in subclasses to customize hook attachment.
|
|
111
|
-
*
|
|
112
|
-
* @param {object} hookManager - Hook manager instance
|
|
113
|
-
* @protected
|
|
114
|
-
*/
|
|
115
|
-
#attachHooksToAction(hookManager) {
|
|
116
|
-
// Basic hook attachment - can be overridden by subclasses
|
|
117
|
-
this.action.hook = hookManager.on?.bind(hookManager)
|
|
118
|
-
this.action.hooks = hookManager.hooks
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Setup the action by creating and configuring the runner.
|
|
123
|
-
* Override setupActionInstance() in subclasses for custom setup logic.
|
|
124
|
-
*
|
|
125
|
-
* @returns {Promise<void>}
|
|
126
|
-
*/
|
|
127
|
-
async setupAction() {
|
|
128
|
-
this.#debug("Setting up action for %s on %s", 2, this.action.meta?.kind, this.id)
|
|
129
|
-
|
|
130
|
-
await this.#setupHooks()
|
|
131
|
-
await this.#setupActionInstance()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Setup the action instance and create the runner.
|
|
136
|
-
* Override in subclasses to customize action setup.
|
|
137
|
-
*
|
|
138
|
-
* @protected
|
|
139
|
-
*/
|
|
140
|
-
async #setupActionInstance() {
|
|
141
|
-
const actionInstance = new this.action()
|
|
142
|
-
const setup = actionInstance?.setup
|
|
143
|
-
|
|
144
|
-
// Setup is required for actions.
|
|
145
|
-
if (Data.typeOf(setup) === "Function") {
|
|
146
|
-
const builder = new ActionBuilder(actionInstance)
|
|
147
|
-
const configuredBuilder = setup(builder)
|
|
148
|
-
const buildResult = configuredBuilder.build()
|
|
149
|
-
const runner = new ActionRunner({
|
|
150
|
-
action: buildResult.action,
|
|
151
|
-
build: buildResult.build,
|
|
152
|
-
logger: this.#log
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
this.#runner = runner
|
|
156
|
-
} else {
|
|
157
|
-
throw Sass.new("Action setup must be a function.")
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Run the action with the provided input.
|
|
163
|
-
*
|
|
164
|
-
* @param {unknown} result - Input to pass to the action
|
|
165
|
-
* @returns {Promise<unknown>} Action result
|
|
166
|
-
*/
|
|
167
|
-
async runAction(result) {
|
|
168
|
-
if (!this.#runner)
|
|
169
|
-
throw new Error("Action not set up. Call setupAction() first.")
|
|
170
|
-
|
|
171
|
-
return await this.#runner.run(result)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Cleanup the action and hooks.
|
|
176
|
-
*
|
|
177
|
-
* @returns {Promise<void>}
|
|
178
|
-
*/
|
|
179
|
-
async cleanupAction() {
|
|
180
|
-
this.#debug("Cleaning up action for %s on %s", 2, this.action.meta?.kind, this.id)
|
|
181
|
-
|
|
182
|
-
await this.#cleanupHooks()
|
|
183
|
-
await this.#cleanupActionInstance()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Setup hooks if hook manager is present.
|
|
188
|
-
* Override in subclasses to customize hook setup.
|
|
189
|
-
*
|
|
190
|
-
* @protected
|
|
191
|
-
*/
|
|
192
|
-
async #setupHooks() {
|
|
193
|
-
const setup = this.#hookManager?.setup
|
|
194
|
-
|
|
195
|
-
const type = Data.typeOf(setup)
|
|
196
|
-
|
|
197
|
-
// No hooks attached.
|
|
198
|
-
if (type === "Null" || type === "Undefined")
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
if (type !== "Function")
|
|
202
|
-
throw Sass.new("Hook setup must be a function.")
|
|
203
|
-
|
|
204
|
-
await setup.call(
|
|
205
|
-
this.hookManager.hooks, {
|
|
206
|
-
action: this.action,
|
|
207
|
-
variables: this.#variables,
|
|
208
|
-
log: this.#log
|
|
209
|
-
}
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Cleanup hooks if hook manager is present.
|
|
215
|
-
* Override in subclasses to customize hook cleanup.
|
|
216
|
-
*
|
|
217
|
-
* @protected
|
|
218
|
-
*/
|
|
219
|
-
async #cleanupHooks() {
|
|
220
|
-
const cleanup = this.hookManager?.cleanup
|
|
221
|
-
|
|
222
|
-
if (!cleanup)
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
await cleanup.call(this.hookManager.hooks)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Cleanup the action instance.
|
|
230
|
-
* Override in subclasses to add custom cleanup logic.
|
|
231
|
-
*
|
|
232
|
-
* @protected
|
|
233
|
-
*/
|
|
234
|
-
async #cleanupActionInstance() {
|
|
235
|
-
const cleanup = this.action?.cleanup
|
|
236
|
-
|
|
237
|
-
if (!cleanup)
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
await cleanup.call(this.action)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
toString() {
|
|
244
|
-
return `${this.#file?.module || "UNDEFINED"} (${this.meta?.action || "UNDEFINED"})`
|
|
245
|
-
}
|
|
246
|
-
}
|