@gesslar/toolkit 0.4.0 → 0.6.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 +2 -1
- package/src/index.js +3 -0
- package/src/lib/Contract.js +257 -0
- package/src/lib/Data.js +24 -15
- package/src/lib/Glog.js +21 -5
- package/src/lib/Logger.js +3 -0
- package/src/lib/Schemer.js +89 -0
- package/src/lib/Terms.js +74 -0
- package/src/lib/Util.js +21 -0
- package/src/types/Collection.d.ts +6 -1
- package/src/types/Contract.d.ts +162 -0
- package/src/types/Data.d.ts +23 -23
- package/src/types/FS.d.ts +3 -3
- package/src/types/Glog.d.ts +302 -49
- package/src/types/Sass.d.ts +1 -1
- package/src/types/Schemer.d.ts +179 -0
- package/src/types/Tantrum.d.ts +10 -10
- package/src/types/Term.d.ts +1 -1
- package/src/types/Terms.d.ts +145 -0
- package/src/types/Type.d.ts +1 -1
- package/src/types/Util.d.ts +20 -2
- package/src/types/index.d.ts +3 -0
- package/src/lib/ActionBuilder.js +0 -144
- package/src/lib/ActionRunner.js +0 -109
- package/src/lib/BaseActionManager.js +0 -246
- package/src/lib/BaseHookManager.js +0 -209
- package/src/lib/Piper.js +0 -181
|
@@ -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/Type.d.ts
CHANGED
|
@@ -23,4 +23,4 @@ export default class TypeSpec {
|
|
|
23
23
|
reduce<T>(callback: (acc: T, spec: TypeSpecDefinition) => T, initialValue: T): T
|
|
24
24
|
find(callback: (spec: TypeSpecDefinition) => boolean): TypeSpecDefinition | undefined
|
|
25
25
|
match(value: unknown, options?: { allowEmpty?: boolean }): boolean
|
|
26
|
-
}
|
|
26
|
+
}
|
package/src/types/Util.d.ts
CHANGED
|
@@ -54,6 +54,16 @@ declare class Util {
|
|
|
54
54
|
*/
|
|
55
55
|
static rightAlignText(text: string | number, width?: number): string
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Centre-align a string inside a fixed width (pad with spaces on left).
|
|
59
|
+
* If the string exceeds width it is returned unchanged.
|
|
60
|
+
*
|
|
61
|
+
* @param text - Text to align.
|
|
62
|
+
* @param width - Target field width (default 80).
|
|
63
|
+
* @returns Padded string with text centred.
|
|
64
|
+
*/
|
|
65
|
+
static centreAlignText(text: string | number, width?: number): string
|
|
66
|
+
|
|
57
67
|
/**
|
|
58
68
|
* Compute sha256 hash (hex) of the provided string.
|
|
59
69
|
*
|
|
@@ -174,7 +184,15 @@ declare class Util {
|
|
|
174
184
|
* @param args - Arguments to pass to event listeners
|
|
175
185
|
* @returns Resolves when all listeners have completed
|
|
176
186
|
*/
|
|
177
|
-
static asyncEmitAnon(
|
|
187
|
+
static asyncEmitAnon(
|
|
188
|
+
emitter: {
|
|
189
|
+
listeners(event: string): Function[],
|
|
190
|
+
on(event: string, listener: Function): any,
|
|
191
|
+
emit(event: string, ...args: unknown[]): any
|
|
192
|
+
},
|
|
193
|
+
event: string,
|
|
194
|
+
...args: unknown[]
|
|
195
|
+
): Promise<void>
|
|
178
196
|
|
|
179
197
|
/**
|
|
180
198
|
* Determine the Levenshtein distance between two string values.
|
|
@@ -228,7 +246,7 @@ declare class Util {
|
|
|
228
246
|
* @param trim - Whether to trim whitespace from each line (default: true)
|
|
229
247
|
* @param flags - Array of regex flags to apply (default: [])
|
|
230
248
|
* @returns A new RegExp object with the processed pattern
|
|
231
|
-
*
|
|
249
|
+
*
|
|
232
250
|
* @throws Will throw if input is not a string
|
|
233
251
|
* @throws Will throw if trim is not a boolean
|
|
234
252
|
* @throws Will throw if flags is not an array
|
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'
|
package/src/lib/ActionBuilder.js
DELETED
|
@@ -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
|
-
}
|
package/src/lib/ActionRunner.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import ActionBuilder, {ACTIVITY} from "./ActionBuilder.js"
|
|
2
|
-
import Data from "./Data.js"
|
|
3
|
-
import Piper from "./Piper.js"
|
|
4
|
-
import Sass from "./Sass.js"
|
|
5
|
-
import Glog from "./Glog.js"
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Orchestrates execution of {@link ActionBuilder}-produced pipelines.
|
|
9
|
-
*
|
|
10
|
-
* Activities run in insertion order, with support for once-off work, repeated
|
|
11
|
-
* loops, and nested parallel pipelines. Each activity receives a mutable
|
|
12
|
-
* context object under `result.value` that can be replaced or enriched.
|
|
13
|
-
*/
|
|
14
|
-
export default class ActionRunner {
|
|
15
|
-
#action = null
|
|
16
|
-
#build = null
|
|
17
|
-
#logger = null
|
|
18
|
-
|
|
19
|
-
constructor({action, build, logger}) {
|
|
20
|
-
this.#action = action
|
|
21
|
-
this.#build = build
|
|
22
|
-
this.#logger = logger ?? {newDebug: () => () => {}}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Executes the configured action pipeline.
|
|
27
|
-
*
|
|
28
|
-
* @param {unknown} content Seed value passed to the first activity.
|
|
29
|
-
* @returns {Promise<unknown>} Final value produced by the pipeline, or null when a parallel stage reports failures.
|
|
30
|
-
* @throws {Sass} When no activities are registered or required parallel builders are missing.
|
|
31
|
-
*/
|
|
32
|
-
async run(content) {
|
|
33
|
-
const AR = ActionRunner
|
|
34
|
-
const result = {value: content}
|
|
35
|
-
const action = this.#action
|
|
36
|
-
const activities = this.#build.activities
|
|
37
|
-
|
|
38
|
-
if(!activities.size)
|
|
39
|
-
throw Sass.new("No activities defined in action.")
|
|
40
|
-
|
|
41
|
-
for(const [_,activity] of activities) {
|
|
42
|
-
const {op} = activity
|
|
43
|
-
|
|
44
|
-
if(activity.kind === ACTIVITY.ONCE) {
|
|
45
|
-
|
|
46
|
-
if(Data.typeOf(activity.hooks?.before) === "Function")
|
|
47
|
-
await activity.hooks.before.call(action, result)
|
|
48
|
-
|
|
49
|
-
const activityResult = await op.call(action, result)
|
|
50
|
-
|
|
51
|
-
if(!activityResult)
|
|
52
|
-
break
|
|
53
|
-
|
|
54
|
-
if(Data.typeOf(activity.hooks?.after) === "Function")
|
|
55
|
-
await activity.hooks.after.call(action, result)
|
|
56
|
-
|
|
57
|
-
} else if(activity.kind == ACTIVITY.MANY) {
|
|
58
|
-
for(;;) {
|
|
59
|
-
|
|
60
|
-
if(Data.typeOf(activity.hooks?.before) === "Function")
|
|
61
|
-
await activity.hooks.before.call(action, result)
|
|
62
|
-
|
|
63
|
-
const activityResult = await op.call(action, result)
|
|
64
|
-
|
|
65
|
-
if(!activityResult)
|
|
66
|
-
break
|
|
67
|
-
|
|
68
|
-
if(Data.typeOf(activity.hooks?.after) === "Function")
|
|
69
|
-
await activity.hooks.after.call(action, result)
|
|
70
|
-
}
|
|
71
|
-
} else if(activity.kind === ACTIVITY.PARALLEL) {
|
|
72
|
-
if(op === undefined)
|
|
73
|
-
throw Sass.new("Missing action builder. Did you return the builder?")
|
|
74
|
-
|
|
75
|
-
if(!op)
|
|
76
|
-
throw Sass.new("Okay, cheeky monkey, you need to return the builder for this to work.")
|
|
77
|
-
|
|
78
|
-
const piper = new Piper({logger: this.#logger})
|
|
79
|
-
.addStep(c => new AR(op.build()).run(c))
|
|
80
|
-
|
|
81
|
-
result.value = await piper.pipe()
|
|
82
|
-
Glog(result)
|
|
83
|
-
throw Sass.new("Nope")
|
|
84
|
-
|
|
85
|
-
// // wheeeeeeeeeeeeee! ZOOMZOOM!
|
|
86
|
-
// const settled = await Util.settleAll(
|
|
87
|
-
// result.value.map()
|
|
88
|
-
// )
|
|
89
|
-
|
|
90
|
-
// const rejected = settled
|
|
91
|
-
// .filter(r => r.status === "rejected")
|
|
92
|
-
// .map(r => {
|
|
93
|
-
// return r.reason instanceof Sass
|
|
94
|
-
// ? r.reason
|
|
95
|
-
// : Sass.new("Running structured parsing.", r.reason)
|
|
96
|
-
// })
|
|
97
|
-
// .map(r => r.report(true))
|
|
98
|
-
|
|
99
|
-
// if(rejected.length)
|
|
100
|
-
// return null
|
|
101
|
-
|
|
102
|
-
// result.value = settled.map(s => s.value)
|
|
103
|
-
// .sort((a,b) => a.index-b.index)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return result.value
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -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
|
-
}
|