@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.
@@ -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
- /** Write content to a file */
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
- /** Load an object from JSON5 or YAML file with type specification */
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
@@ -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
- }