@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
package/src/lib/Piper.js
CHANGED
|
@@ -9,31 +9,36 @@
|
|
|
9
9
|
* - Error handling and reporting
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import Glog from "./Glog.js"
|
|
13
|
+
import Sass from "./Sass.js"
|
|
14
|
+
import Tantrum from "./Tantrum.js"
|
|
15
|
+
import Util from "./Util.js"
|
|
16
|
+
|
|
12
17
|
export default class Piper {
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
constructor(
|
|
22
|
-
this.#
|
|
18
|
+
#debug
|
|
19
|
+
|
|
20
|
+
#lifeCycle = new Map([
|
|
21
|
+
["setup", new Set()],
|
|
22
|
+
["process", new Set()],
|
|
23
|
+
["teardown", new Set()]
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
constructor(arg) {
|
|
27
|
+
this.#debug = arg?.debug ?? new Glog().newDebug("[PIPER]")
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* Add a processing step to the pipeline
|
|
27
32
|
*
|
|
28
|
-
* @param {(context: object) => Promise<object>}
|
|
33
|
+
* @param {(context: object) => Promise<object>} fn - Function that processes an item: (context) => Promise<result>
|
|
29
34
|
* @param {object} options - Step options (name, required, etc.)
|
|
30
35
|
* @returns {Piper} The pipeline instance (for chaining)
|
|
31
36
|
*/
|
|
32
|
-
addStep(
|
|
33
|
-
this.#
|
|
34
|
-
fn
|
|
35
|
-
name: options.name || `Step ${this.#
|
|
36
|
-
required: options.required
|
|
37
|
+
addStep(fn, options = {}) {
|
|
38
|
+
this.#lifeCycle.get("process").add({
|
|
39
|
+
fn,
|
|
40
|
+
name: options.name || `Step ${this.#lifeCycle.get("process").size + 1}`,
|
|
41
|
+
required: !!options.required, // Default to required
|
|
37
42
|
...options
|
|
38
43
|
})
|
|
39
44
|
|
|
@@ -43,11 +48,11 @@ export default class Piper {
|
|
|
43
48
|
/**
|
|
44
49
|
* Add setup hook that runs before processing starts
|
|
45
50
|
*
|
|
46
|
-
* @param {() => Promise<void>}
|
|
51
|
+
* @param {() => Promise<void>} fn - Setup function: () => Promise<void>
|
|
47
52
|
* @returns {Piper} The pipeline instance (for chaining)
|
|
48
53
|
*/
|
|
49
|
-
addSetup(
|
|
50
|
-
this.#
|
|
54
|
+
addSetup(fn) {
|
|
55
|
+
this.#lifeCycle.get("setup").add(fn)
|
|
51
56
|
|
|
52
57
|
return this
|
|
53
58
|
}
|
|
@@ -55,11 +60,11 @@ export default class Piper {
|
|
|
55
60
|
/**
|
|
56
61
|
* Add cleanup hook that runs after processing completes
|
|
57
62
|
*
|
|
58
|
-
* @param {() => Promise<void>}
|
|
63
|
+
* @param {() => Promise<void>} fn - Cleanup function: () => Promise<void>
|
|
59
64
|
* @returns {Piper} The pipeline instance (for chaining)
|
|
60
65
|
*/
|
|
61
|
-
addCleanup(
|
|
62
|
-
this.#
|
|
66
|
+
addCleanup(fn) {
|
|
67
|
+
this.#lifeCycle.get("teardown").add(fn)
|
|
63
68
|
|
|
64
69
|
return this
|
|
65
70
|
}
|
|
@@ -72,50 +77,54 @@ export default class Piper {
|
|
|
72
77
|
* @returns {Promise<object>} - Results object with succeeded, warned, errored arrays
|
|
73
78
|
*/
|
|
74
79
|
async pipe(items, maxConcurrent = 10) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
items.forEach(item => item.pipeStamp = Symbol(performance.now()))
|
|
81
|
+
|
|
82
|
+
let itemIndex = 0
|
|
83
|
+
const allResults = []
|
|
84
|
+
|
|
85
|
+
const processWorker = async() => {
|
|
86
|
+
while(true) {
|
|
87
|
+
const currentIndex = itemIndex++
|
|
88
|
+
if(currentIndex >= items.length)
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
const item = items[currentIndex]
|
|
92
|
+
try {
|
|
93
|
+
const result = await this.#processItem(item)
|
|
94
|
+
allResults.push(result)
|
|
95
|
+
} catch(error) {
|
|
96
|
+
throw Sass.new("Processing pipeline item.", error)
|
|
90
97
|
}
|
|
91
|
-
|
|
92
|
-
// Process next item if queue has items
|
|
93
|
-
if(itemQueue.length > 0) {
|
|
94
|
-
const nextItem = itemQueue.shift()
|
|
95
|
-
|
|
96
|
-
return processNextItem(nextItem)
|
|
97
|
-
}
|
|
98
|
-
})
|
|
98
|
+
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const item = itemQueue.shift()
|
|
101
|
+
const setupResult = await Util.settleAll([...this.#lifeCycle.get("setup")].map(e => e()))
|
|
102
|
+
this.#processResult("Setting up the pipeline.", setupResult)
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
// Start workers up to maxConcurrent limit
|
|
105
|
+
const workers = []
|
|
106
|
+
const workerCount = Math.min(maxConcurrent, items.length)
|
|
107
|
+
|
|
108
|
+
for(let i = 0; i < workerCount; i++)
|
|
109
|
+
workers.push(processWorker())
|
|
107
110
|
|
|
108
|
-
// Wait for all
|
|
109
|
-
await
|
|
111
|
+
// Wait for all workers to complete
|
|
112
|
+
const processResult = await Util.settleAll(workers)
|
|
113
|
+
this.#processResult("Processing pipeline.", processResult)
|
|
110
114
|
|
|
111
115
|
// Run cleanup hooks
|
|
112
|
-
await
|
|
116
|
+
const teardownResult = await Util.settleAll([...this.#lifeCycle.get("teardown")].map(e => e()))
|
|
117
|
+
this.#processResult("Tearing down the pipeline.", teardownResult)
|
|
113
118
|
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
return allResults
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#processResult(message, settled) {
|
|
123
|
+
if(settled.some(r => r.status === "rejected"))
|
|
124
|
+
throw Tantrum.new(
|
|
125
|
+
message,
|
|
126
|
+
settled.filter(r => r.status==="rejected").map(r => r.reason)
|
|
127
|
+
)
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
/**
|
|
@@ -126,56 +135,21 @@ export default class Piper {
|
|
|
126
135
|
* @private
|
|
127
136
|
*/
|
|
128
137
|
async #processItem(item) {
|
|
129
|
-
const debug = this.#
|
|
130
|
-
const context = {item, data: {}}
|
|
138
|
+
const debug = this.#debug
|
|
131
139
|
|
|
132
140
|
try {
|
|
133
141
|
// Execute each step in sequence
|
|
134
|
-
|
|
135
|
-
debug(`Executing step: ${step.name}`, 2)
|
|
136
|
-
|
|
137
|
-
const result = await step.fn(context)
|
|
142
|
+
let result = item
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if(result.status === "error") {
|
|
142
|
-
return result
|
|
143
|
-
}
|
|
144
|
+
for(const step of this.#lifeCycle.get("process")) {
|
|
145
|
+
debug("Executing step: %o", 2, step.name)
|
|
144
146
|
|
|
145
|
-
|
|
146
|
-
return result
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Merge result data into context for next steps
|
|
150
|
-
context.data = {...context.data, ...result.data}
|
|
151
|
-
context.status = result.status || context.status
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
status: context.status || "success",
|
|
157
|
-
...context.data
|
|
147
|
+
result = await step.fn(result) ?? result
|
|
158
148
|
}
|
|
159
149
|
|
|
150
|
+
return result
|
|
160
151
|
} catch(error) {
|
|
161
|
-
|
|
162
|
-
status: "error",
|
|
163
|
-
error,
|
|
164
|
-
item
|
|
165
|
-
}
|
|
152
|
+
throw Sass.new("Processing an item.", error)
|
|
166
153
|
}
|
|
167
154
|
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Clear results (useful for reusing pipeline instance)
|
|
171
|
-
*
|
|
172
|
-
* @returns {Piper} The pipeline instance (for chaining)
|
|
173
|
-
*/
|
|
174
|
-
clearResults() {
|
|
175
|
-
this.#succeeded = []
|
|
176
|
-
this.#warned = []
|
|
177
|
-
this.#errored = []
|
|
178
|
-
|
|
179
|
-
return this
|
|
180
|
-
}
|
|
181
155
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import Ajv from "ajv"
|
|
2
|
+
|
|
3
|
+
import Data from "./Data.js"
|
|
4
|
+
import Util from "./Util.js"
|
|
5
|
+
import Valid from "./Valid.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schemer provides utilities for compiling and validating JSON schemas using AJV.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* - Use Schemer.fromFile(file, options) to create a validator from a file.
|
|
12
|
+
* - Use Schemer.from(schemaData, options) to create a validator from a schema object.
|
|
13
|
+
* - Use Schemer.getValidator(schema, options) to get a raw AJV validator function.
|
|
14
|
+
* - Use Schemer.reportValidationErrors(errors) to format AJV validation errors.
|
|
15
|
+
*/
|
|
16
|
+
export default class Schemer {
|
|
17
|
+
static async fromFile(file, options={}) {
|
|
18
|
+
Valid.type(file, "FileObject")
|
|
19
|
+
Valid.assert(Data.isPlainObject(options), "Options must be a plain object.")
|
|
20
|
+
|
|
21
|
+
const schemaData = await file.loadData()
|
|
22
|
+
|
|
23
|
+
return Schemer.getValidator(schemaData, options)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async from(schemaData={}, options={}) {
|
|
27
|
+
Valid.assert(Data.isPlainObject(schemaData), "Schema data must be a plain object.")
|
|
28
|
+
Valid.assert(Data.isPlainObject(options), "Options must be a plain object.")
|
|
29
|
+
|
|
30
|
+
return Schemer.getValidator(schemaData, options)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a validator function from a schema object
|
|
35
|
+
*
|
|
36
|
+
* @param {object} schema - The schema to compile
|
|
37
|
+
* @param {object} [options] - AJV options
|
|
38
|
+
* @returns {(data: unknown) => boolean} The AJV validator function, which may have additional properties (e.g., `.errors`)
|
|
39
|
+
*/
|
|
40
|
+
static getValidator(schema, options = {allErrors: true, verbose: true}) {
|
|
41
|
+
const ajv = new Ajv(options)
|
|
42
|
+
|
|
43
|
+
return ajv.compile(schema)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static reportValidationErrors(errors) {
|
|
47
|
+
if(!errors) {
|
|
48
|
+
return ""
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return errors.reduce((errorMessages, error) => {
|
|
52
|
+
let msg = `- "${error.instancePath || "(root)"}" ${error.message}`
|
|
53
|
+
|
|
54
|
+
if(error.params) {
|
|
55
|
+
const details = []
|
|
56
|
+
|
|
57
|
+
if(error.params.type)
|
|
58
|
+
details.push(` ➜ Expected type: ${error.params.type}`)
|
|
59
|
+
|
|
60
|
+
if(error.params.missingProperty)
|
|
61
|
+
details.push(` ➜ Missing required field: ${error.params.missingProperty}`)
|
|
62
|
+
|
|
63
|
+
if(error.params.allowedValues) {
|
|
64
|
+
details.push(` ➜ Allowed values: "${error.params.allowedValues.join('", "')}"`)
|
|
65
|
+
details.push(` ➜ Received value: "${error.data}"`)
|
|
66
|
+
const closestMatch =
|
|
67
|
+
Util.findClosestMatch(error.data, error.params.allowedValues)
|
|
68
|
+
|
|
69
|
+
if(closestMatch)
|
|
70
|
+
details.push(` ➜ Did you mean: "${closestMatch}"?`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if(error.params.pattern)
|
|
74
|
+
details.push(` ➜ Expected pattern: ${error.params.pattern}`)
|
|
75
|
+
|
|
76
|
+
if(error.params.format)
|
|
77
|
+
details.push(` ➜ Expected format: ${error.params.format}`)
|
|
78
|
+
|
|
79
|
+
if(error.params.additionalProperty)
|
|
80
|
+
details.push(` ➜ Unexpected property: ${error.params.additionalProperty}`)
|
|
81
|
+
|
|
82
|
+
if(details.length)
|
|
83
|
+
msg += `\n${details.join("\n")}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return errorMessages ? `${errorMessages}\n${msg}` : msg
|
|
87
|
+
}, "")
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/lib/Terms.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import JSON5 from "json5"
|
|
2
|
+
import yaml from "yaml"
|
|
3
|
+
|
|
4
|
+
import Data from "./Data.js"
|
|
5
|
+
import DirectoryObject from "./DirectoryObject.js"
|
|
6
|
+
import FileObject from "./FileObject.js"
|
|
7
|
+
import Sass from "./Sass.js"
|
|
8
|
+
import Valid from "./Valid.js"
|
|
9
|
+
|
|
10
|
+
const refex = /^ref:\/\/(?<file>.*)$/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Terms represents an interface definition - what an action promises to provide or accept.
|
|
14
|
+
* It's just the specification, not the negotiation. Contract handles the negotiation.
|
|
15
|
+
*/
|
|
16
|
+
export default class Terms {
|
|
17
|
+
#definition = null
|
|
18
|
+
|
|
19
|
+
constructor(definition) {
|
|
20
|
+
this.#definition = definition
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses terms data, handling file references
|
|
25
|
+
*
|
|
26
|
+
* @param {string|object} termsData - Terms data or reference
|
|
27
|
+
* @param {DirectoryObject?} directoryObject - Directory context for file resolution
|
|
28
|
+
* @returns {object} Parsed terms data
|
|
29
|
+
*/
|
|
30
|
+
static async parse(termsData, directoryObject) {
|
|
31
|
+
if(Data.isBaseType(termsData, "String")) {
|
|
32
|
+
const match = refex.exec(termsData)
|
|
33
|
+
|
|
34
|
+
if(match?.groups?.file) {
|
|
35
|
+
Valid.type(directoryObject, "DirectoryObject")
|
|
36
|
+
|
|
37
|
+
const file = new FileObject(match.groups.file, directoryObject)
|
|
38
|
+
|
|
39
|
+
return await file.loadData()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Try parsing as YAML/JSON
|
|
43
|
+
try {
|
|
44
|
+
const result = JSON5.parse(termsData)
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
const result = yaml.parse(termsData)
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
} catch {
|
|
53
|
+
throw Sass.new(`Could not parse terms data as YAML or JSON: ${termsData}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if(Data.isBaseType(termsData, "Object")) {
|
|
59
|
+
return termsData
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw Sass.new(`Invalid terms data type: ${typeof termsData}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the terms definition
|
|
67
|
+
*
|
|
68
|
+
* @returns {object} The terms definition
|
|
69
|
+
*/
|
|
70
|
+
get definition() {
|
|
71
|
+
return this.#definition
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Implementation: ../lib/Contract.js
|
|
2
|
+
|
|
3
|
+
import type { ValidateFunction } from 'ajv'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Debug function type for Contract operations
|
|
7
|
+
*/
|
|
8
|
+
export type DebugFunction = (message: string, level?: number, ...args: unknown[]) => void
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Contract represents a successful negotiation between Terms.
|
|
12
|
+
* It handles validation and compatibility checking between what
|
|
13
|
+
* one action provides and what another accepts.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Two-party contract between provider and consumer
|
|
18
|
+
* const provider = new Terms(providerDefinition)
|
|
19
|
+
* const consumer = new Terms(consumerDefinition)
|
|
20
|
+
* const contract = new Contract(provider, consumer, { debug: console.log })
|
|
21
|
+
*
|
|
22
|
+
* // Validate data against the contract
|
|
23
|
+
* const isValid = contract.validate(someData)
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Single-party contract from terms definition
|
|
29
|
+
* const contract = Contract.fromTerms("parser", {
|
|
30
|
+
* provides: {
|
|
31
|
+
* type: "object",
|
|
32
|
+
* properties: {
|
|
33
|
+
* name: { type: "string" },
|
|
34
|
+
* age: { type: "number" }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* contract.validate({ name: "John", age: 30 }) // true
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare class Contract {
|
|
43
|
+
/**
|
|
44
|
+
* Creates a contract by negotiating between provider and consumer terms
|
|
45
|
+
*
|
|
46
|
+
* @param providerTerms - What the provider offers
|
|
47
|
+
* @param consumerTerms - What the consumer expects
|
|
48
|
+
* @param options - Configuration options
|
|
49
|
+
* @param options.debug - Debug function for logging negotiation details
|
|
50
|
+
*
|
|
51
|
+
* @throws {Sass} If contract negotiation fails due to incompatible terms
|
|
52
|
+
*/
|
|
53
|
+
constructor(
|
|
54
|
+
providerTerms: import('./Terms.js').default | null,
|
|
55
|
+
consumerTerms: import('./Terms.js').default | null,
|
|
56
|
+
options?: { debug?: DebugFunction }
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a contract from terms with schema validation
|
|
61
|
+
*
|
|
62
|
+
* @param name - Contract identifier for error reporting
|
|
63
|
+
* @param termsDefinition - The terms definition object
|
|
64
|
+
* @param validator - Optional AJV schema validator function with .errors property
|
|
65
|
+
* @param debug - Debug function for logging validation details
|
|
66
|
+
* @returns New contract instance ready for data validation
|
|
67
|
+
*
|
|
68
|
+
* @throws {Sass} If terms definition is invalid according to the validator
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const contract = Contract.fromTerms("user-parser", {
|
|
73
|
+
* provides: {
|
|
74
|
+
* type: "object",
|
|
75
|
+
* properties: {
|
|
76
|
+
* id: { type: "string" },
|
|
77
|
+
* name: { type: "string" }
|
|
78
|
+
* },
|
|
79
|
+
* required: ["id", "name"]
|
|
80
|
+
* }
|
|
81
|
+
* })
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
static fromTerms(
|
|
85
|
+
name: string,
|
|
86
|
+
termsDefinition: object,
|
|
87
|
+
validator?: ValidateFunction | null,
|
|
88
|
+
debug?: DebugFunction
|
|
89
|
+
): Contract
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates data against this contract's schema
|
|
93
|
+
*
|
|
94
|
+
* @param data - Data object to validate against the contract
|
|
95
|
+
* @returns True if validation passes
|
|
96
|
+
*
|
|
97
|
+
* @throws {Sass} If validation fails with detailed error messages
|
|
98
|
+
* @throws {Sass} If contract has not been successfully negotiated
|
|
99
|
+
* @throws {Sass} If no validator is available for this contract
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* try {
|
|
104
|
+
* contract.validate({ id: "123", name: "John" })
|
|
105
|
+
* console.log("Data is valid!")
|
|
106
|
+
* } catch (error) {
|
|
107
|
+
* console.error("Validation failed:", error.message)
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
validate(data: object): boolean
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if contract negotiation was successful
|
|
115
|
+
*
|
|
116
|
+
* @returns True if the contract has been successfully negotiated
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* if (contract.isNegotiated) {
|
|
121
|
+
* contract.validate(data)
|
|
122
|
+
* } else {
|
|
123
|
+
* console.error("Contract negotiation failed")
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
get isNegotiated(): boolean
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get the provider terms (if any)
|
|
131
|
+
*
|
|
132
|
+
* @returns Provider terms or null for single-party contracts
|
|
133
|
+
*/
|
|
134
|
+
get providerTerms(): import('./Terms.js').default | null
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get the consumer terms (if any)
|
|
138
|
+
*
|
|
139
|
+
* @returns Consumer terms or null for single-party contracts
|
|
140
|
+
*/
|
|
141
|
+
get consumerTerms(): import('./Terms.js').default | null
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get the contract validator function
|
|
145
|
+
*
|
|
146
|
+
* @returns The AJV validator function used by this contract, or null if none available
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const validator = contract.validator
|
|
151
|
+
* if (validator) {
|
|
152
|
+
* const isValid = validator(someData)
|
|
153
|
+
* if (!isValid) {
|
|
154
|
+
* console.log("Validation errors:", validator.errors)
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
get validator(): ((data: object) => boolean) | null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default Contract
|
|
@@ -40,6 +40,12 @@ export default class DirectoryObject extends FS {
|
|
|
40
40
|
/** The directory extension (usually empty) */
|
|
41
41
|
readonly extension: string
|
|
42
42
|
|
|
43
|
+
/** The platform-specific path separator (e.g., '/' on Unix, '\\' on Windows) */
|
|
44
|
+
readonly sep: string
|
|
45
|
+
|
|
46
|
+
/** Array of directory path segments split by separator */
|
|
47
|
+
readonly trail: string[]
|
|
48
|
+
|
|
43
49
|
/** Always false for directories */
|
|
44
50
|
readonly isFile: false
|
|
45
51
|
|
|
@@ -49,6 +55,25 @@ export default class DirectoryObject extends FS {
|
|
|
49
55
|
/** Whether the directory exists (async) */
|
|
50
56
|
readonly exists: Promise<boolean>
|
|
51
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Generator that walks up the directory tree, yielding parent directories.
|
|
60
|
+
* Starts from the current directory and yields each parent until reaching the root.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const dir = new DirectoryObject('/path/to/deep/directory')
|
|
65
|
+
* for (const parent of dir.walkUp) {
|
|
66
|
+
* console.log(parent.path)
|
|
67
|
+
* // /path/to/deep/directory
|
|
68
|
+
* // /path/to/deep
|
|
69
|
+
* // /path/to
|
|
70
|
+
* // /path
|
|
71
|
+
* // /
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
readonly walkUp: Generator<DirectoryObject, void, unknown>
|
|
76
|
+
|
|
52
77
|
/** Returns a string representation of the DirectoryObject */
|
|
53
78
|
toString(): string
|
|
54
79
|
|
|
@@ -64,9 +89,47 @@ export default class DirectoryObject extends FS {
|
|
|
64
89
|
isDirectory: boolean
|
|
65
90
|
}
|
|
66
91
|
|
|
67
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Lists the contents of this directory.
|
|
94
|
+
* Returns FileObject instances for files and DirectoryObject instances for subdirectories.
|
|
95
|
+
*
|
|
96
|
+
* @returns Promise resolving to object with files and directories arrays
|
|
97
|
+
* @throws {Error} If directory cannot be read
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* const dir = new DirectoryObject('./src')
|
|
102
|
+
* const {files, directories} = await dir.read()
|
|
103
|
+
*
|
|
104
|
+
* console.log(`Found ${files.length} files`)
|
|
105
|
+
* files.forEach(file => console.log(file.name))
|
|
106
|
+
*
|
|
107
|
+
* console.log(`Found ${directories.length} subdirectories`)
|
|
108
|
+
* directories.forEach(subdir => console.log(subdir.name))
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
68
111
|
read(): Promise<DirectoryListing>
|
|
69
112
|
|
|
70
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Ensures this directory exists, creating it if necessary.
|
|
115
|
+
* Gracefully handles the case where the directory already exists (EEXIST error).
|
|
116
|
+
* Pass options to control directory creation behavior (e.g., recursive, mode).
|
|
117
|
+
*
|
|
118
|
+
* @param options - Options to pass to fs.mkdir (e.g., {recursive: true, mode: 0o755})
|
|
119
|
+
* @returns Promise that resolves when directory exists or has been created
|
|
120
|
+
* @throws {Sass} If directory creation fails for reasons other than already existing
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const dir = new DirectoryObject('./build/output')
|
|
125
|
+
*
|
|
126
|
+
* // Create directory recursively
|
|
127
|
+
* await dir.assureExists({recursive: true})
|
|
128
|
+
*
|
|
129
|
+
* // Now safe to write files
|
|
130
|
+
* const file = new FileObject('result.json', dir)
|
|
131
|
+
* await file.write(JSON.stringify(data))
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
71
134
|
assureExists(options?: any): Promise<void>
|
|
72
135
|
}
|