@gesslar/bedoc 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE.txt +12 -0
  2. package/README.md +15 -3
  3. package/dist/schema/bedoc.action.json +42 -0
  4. package/dist/types/Action.d.ts +3 -0
  5. package/dist/types/Action.d.ts.map +1 -0
  6. package/dist/types/BeDoc.d.ts +208 -0
  7. package/dist/types/BeDoc.d.ts.map +1 -0
  8. package/dist/types/Configuration.d.ts +11 -0
  9. package/dist/types/Configuration.d.ts.map +1 -0
  10. package/dist/types/ConfigurationParameters.d.ts +3 -0
  11. package/dist/types/ConfigurationParameters.d.ts.map +1 -0
  12. package/dist/types/Conveyor.d.ts +27 -0
  13. package/dist/types/Conveyor.d.ts.map +1 -0
  14. package/dist/types/Discovery.d.ts +215 -0
  15. package/dist/types/Discovery.d.ts.map +1 -0
  16. package/dist/types/Environment.d.ts +3 -0
  17. package/dist/types/Environment.d.ts.map +1 -0
  18. package/dist/types/Logger.d.ts +47 -0
  19. package/dist/types/Logger.d.ts.map +1 -0
  20. package/dist/types/Schema.d.ts +3 -0
  21. package/dist/types/Schema.d.ts.map +1 -0
  22. package/dist/types/cli.d.ts +2 -2
  23. package/dist/types/cli.d.ts.map +1 -10
  24. package/package.json +24 -23
  25. package/src/Action.js +9 -0
  26. package/src/BeDoc.js +276 -0
  27. package/src/CLIOutput.js +198 -0
  28. package/src/{core/Configuration.js → Configuration.js} +72 -58
  29. package/src/{core/ConfigurationParameters.js → ConfigurationParameters.js} +35 -27
  30. package/src/Conveyor.js +256 -0
  31. package/src/Discovery.js +442 -0
  32. package/src/Environment.js +8 -0
  33. package/src/{core/Logger.js → Logger.js} +30 -18
  34. package/src/Schema.js +6 -0
  35. package/src/cli.js +77 -34
  36. package/tsconfig.types.json +42 -0
  37. package/LICENSE +0 -24
  38. package/dist/types/core/ActionManager.d.ts +0 -58
  39. package/dist/types/core/ActionManager.d.ts.map +0 -10
  40. package/dist/types/core/Configuration.d.ts +0 -27
  41. package/dist/types/core/Configuration.d.ts.map +0 -10
  42. package/dist/types/core/ConfigurationParameters.d.ts +0 -38
  43. package/dist/types/core/ConfigurationParameters.d.ts.map +0 -10
  44. package/dist/types/core/Conveyor.d.ts +0 -49
  45. package/dist/types/core/Conveyor.d.ts.map +0 -10
  46. package/dist/types/core/Core.d.ts +0 -48
  47. package/dist/types/core/Core.d.ts.map +0 -10
  48. package/dist/types/core/Discovery.d.ts +0 -73
  49. package/dist/types/core/Discovery.d.ts.map +0 -10
  50. package/dist/types/core/HookManager.d.ts +0 -60
  51. package/dist/types/core/HookManager.d.ts.map +0 -10
  52. package/dist/types/core/Logger.d.ts +0 -63
  53. package/dist/types/core/Logger.d.ts.map +0 -10
  54. package/dist/types/core/action/ParseManager.d.ts +0 -8
  55. package/dist/types/core/action/ParseManager.d.ts.map +0 -10
  56. package/dist/types/core/action/PrintManager.d.ts +0 -8
  57. package/dist/types/core/action/PrintManager.d.ts.map +0 -10
  58. package/dist/types/core/util/ActionUtil.d.ts +0 -35
  59. package/dist/types/core/util/ActionUtil.d.ts.map +0 -10
  60. package/dist/types/core/util/DataUtil.d.ts +0 -52
  61. package/dist/types/core/util/DataUtil.d.ts.map +0 -10
  62. package/dist/types/core/util/FDUtil.d.ts +0 -171
  63. package/dist/types/core/util/FDUtil.d.ts.map +0 -10
  64. package/dist/types/core/util/ModuleUtil.d.ts +0 -27
  65. package/dist/types/core/util/ModuleUtil.d.ts.map +0 -10
  66. package/dist/types/core/util/StringUtil.d.ts +0 -5
  67. package/dist/types/core/util/StringUtil.d.ts.map +0 -10
  68. package/dist/types/core/util/TypeSpec.d.ts +0 -42
  69. package/dist/types/core/util/TypeSpec.d.ts.map +0 -10
  70. package/dist/types/core/util/ValidUtil.d.ts +0 -29
  71. package/dist/types/core/util/ValidUtil.d.ts.map +0 -10
  72. package/src/core/ActionManager.js +0 -147
  73. package/src/core/ContractManager.js +0 -112
  74. package/src/core/Conveyor.js +0 -185
  75. package/src/core/Core.js +0 -166
  76. package/src/core/Discovery.js +0 -403
  77. package/src/core/HookManager.js +0 -143
  78. package/src/core/action/ParseManager.js +0 -7
  79. package/src/core/action/PrintManager.js +0 -7
  80. package/src/core/contract/ParseContract.js +0 -7
  81. package/src/core/contract/PrintContract.js +0 -7
  82. package/src/core/util/ActionUtil.js +0 -53
  83. package/src/core/util/ContractUtil.js +0 -63
  84. package/src/core/util/DataUtil.js +0 -540
  85. package/src/core/util/FDUtil.js +0 -388
  86. package/src/core/util/ModuleUtil.js +0 -40
  87. package/src/core/util/StringUtil.js +0 -11
  88. package/src/core/util/TypeSpec.js +0 -114
  89. package/src/core/util/ValidUtil.js +0 -50
@@ -1,540 +0,0 @@
1
- import Logger from "../Logger.js"
2
- import TypeSpec from "./TypeSpec.js"
3
-
4
- import * as ValidUtil from "./ValidUtil.js"
5
-
6
- const {validType} = ValidUtil
7
-
8
- const primitives = [
9
- // Primitives
10
- "undefined",
11
- "boolean",
12
- "number",
13
- "bigint",
14
- "string",
15
- "symbol",
16
-
17
- // Object Categories from typeof
18
- "object",
19
- "function",
20
- ]
21
-
22
- const constructors = [
23
- // Object Constructors
24
- "Object",
25
- "Array",
26
- "Function",
27
- "Date",
28
- "RegExp",
29
- "Error",
30
- "Map",
31
- "Set",
32
- "WeakMap",
33
- "WeakSet",
34
- "Promise",
35
- "Int8Array",
36
- "Uint8Array",
37
- "Float32Array",
38
- "Float64Array",
39
- ]
40
-
41
- const dataTypes = [...primitives, ...constructors.map(c => c.toLowerCase())]
42
-
43
- const emptyableTypes = ["string", "array", "object"]
44
-
45
- /**
46
- * Appends a string to another string if it does not already end with it.
47
- *
48
- * @param {string} string The string to append to
49
- * @param {string} append The string to append
50
- * @returns {string} The appended string
51
- */
52
- function appendString(string, append) {
53
- return string.endsWith(append) ? string : `${string}${append}`
54
- }
55
-
56
- /**
57
- * Prepends a string to another string if it does not already start with it.
58
- *
59
- * @param {string} string The string to prepend to
60
- * @param {string} prepend The string to prepend
61
- * @returns {string} The prepended string
62
- */
63
- function prependString(string, prepend) {
64
- return string.startsWith(prepend) ? string : `${prepend}${string}`
65
- }
66
-
67
- /**
68
- * Checks if all elements in an array are of a specified type
69
- *
70
- * @param {Array} arr - The array to check
71
- * @param {string} type - The type to check for (optional, defaults to the
72
- * type of the first element)
73
- * @returns {boolean} Whether all elements are of the specified type
74
- */
75
- function isArrayUniform(arr, type) {
76
- return arr.every(
77
- (item, _index, arr) => typeof item === (type || typeof arr[0]),
78
- )
79
- }
80
-
81
- /**
82
- * Checks if an array is unique
83
- *
84
- * @param {Array} arr - The array of which to remove duplicates
85
- * @returns {Array} The unique elements of the array
86
- */
87
- function isArrayUnique(arr) {
88
- return arr.filter((item, index, self) => self.indexOf(item) === index)
89
- }
90
-
91
- /**
92
- * Returns the intersection of two arrays.
93
- *
94
- * @param {Array} arr1 - The first array.
95
- * @param {Array} arr2 - The second array.
96
- * @returns {Array} The intersection of the two arrays.
97
- */
98
- function arrayIntersection(arr1, arr2) {
99
- return arr1.filter(value => arr2.includes(value))
100
- }
101
-
102
- /**
103
- * Pads an array to a specified length with a value. This operation
104
- * occurs in-place.
105
- *
106
- * @param {Array} arr - The array to pad.
107
- * @param {number} length - The length to pad the array to.
108
- * @param {any} value - The value to pad the array with.
109
- * @param {number} position - The position to pad the array at.
110
- * @returns {Array} The padded array.
111
- */
112
- function arrayPad(arr, length, value, position = 0) {
113
- const diff = length - arr.length
114
- if(diff <= 0) return arr
115
-
116
- const padding = Array(diff).fill(value)
117
-
118
- if(position === 0)
119
- // prepend - default
120
- return padding.concat(arr)
121
- else if(position === -1)
122
- // append
123
- return arr.concat(padding) // somewhere in the middle - THAT IS ILLEGAL
124
- else throw new SyntaxError("Invalid position")
125
- }
126
-
127
- /**
128
- * Clones an object
129
- *
130
- * @param {object} obj - The object to clone
131
- * @param {boolean} freeze - Whether to freeze the cloned object
132
- * @returns {object} The cloned object
133
- */
134
- function cloneObject(obj, freeze = false) {
135
- const result = {}
136
-
137
- for(const [key, value] of Object.entries(obj)) {
138
- if(isType(value, "object"))
139
- result[key] = cloneObject(value)
140
- else
141
- result[key] = value
142
- }
143
-
144
- return freeze ? Object.freeze(result) : result
145
- }
146
-
147
- /**
148
- * Allocates an object from a source array and a spec array or function.
149
- *
150
- * @param {any} source The source array
151
- * @param {any|Function} spec The spec array or function
152
- * @returns {Promise<object>} The allocated object
153
- */
154
- async function allocateObject(source, spec) {
155
- // Data
156
- const workSource = [],
157
- workSpec = [],
158
- result = {}
159
-
160
- if(!isType(source, "array", {allowEmpty: false}))
161
- throw new Error("Source must be an array.")
162
-
163
- workSource.push(...source)
164
-
165
- if(
166
- !isType(spec, "array", {allowEmpty: false}) &&
167
- !isType(spec, "function")
168
- )
169
- throw new Error("Spec must be an array or a function.")
170
-
171
- if(isType(spec, "function")) {
172
- const specResult = await spec(workSource)
173
-
174
- if(!isType(specResult, "array"))
175
- throw new Error("Spec resulting from function must be an array.")
176
-
177
- workSpec.push(...specResult)
178
- } else if(isType(spec, "array", {allowEmpty: false})) {
179
- workSpec.push(...spec)
180
- }
181
-
182
- if(workSource.length !== workSpec.length)
183
- throw new Error("Source and spec must have the same number of elements.")
184
-
185
- // Objects must always be indexed by strings.
186
- workSource.map((element, index, arr) => (arr[index] = String(element)))
187
-
188
- // Check that all keys are strings
189
- if(!isArrayUniform(workSource, "string"))
190
- throw new Error("Indices of an Object must be of type string.")
191
-
192
- workSource.forEach((element, index) => (result[element] = workSpec[index]))
193
-
194
- return result
195
- }
196
-
197
- /**
198
- * Maps an object using a transformer function
199
- *
200
- * @param {object} original The original object
201
- * @param {Function} transformer The transformer function
202
- * @param {boolean} mutate Whether to mutate the original object
203
- * @returns {Promise<object>} The mapped object
204
- */
205
- async function mapObject(original, transformer, mutate = false) {
206
- validType(original, "object", true)
207
- validType(transformer, "function")
208
- validType(mutate, "boolean")
209
-
210
- const result = mutate ? original : {}
211
-
212
- for(const [key, value] of Object.entries(original))
213
- result[key] = isType(value, "object")
214
- ? await mapObject(value, transformer, mutate)
215
- : (result[key] = await transformer(key, value))
216
-
217
- return result
218
- }
219
-
220
- /**
221
- * Checks if an object is empty
222
- *
223
- * @param {object} obj - The object to check
224
- * @returns {boolean} Whether the object is empty
225
- */
226
- function isObjectEmpty(obj) {
227
- return Object.keys(obj).length === 0
228
- }
229
-
230
- /**
231
- * Creates a type spec from a string. A type spec is an array of objects
232
- * defining the type of a value and whether an array is expected.
233
- *
234
- * @param {string} string - The string to parse into a type spec.
235
- * @param {object} options - Additional options for parsing.
236
- * @returns {object[]} An array of type specs.
237
- */
238
- function newTypeSpec(string, options) {
239
- return new TypeSpec(string, options)
240
- }
241
-
242
- /**
243
- * Checks if a value is of a specified type
244
- *
245
- * @param {any} value The value to check
246
- * @param {string|TypeSpec} type The type to check for
247
- * @param {object} options Additional options for checking
248
- * @returns {boolean} Whether the value is of the specified type
249
- */
250
- function isType(value, type, options = {}) {
251
- const typeSpec = type instanceof TypeSpec ? type : newTypeSpec(type, options)
252
- // we're comparing a typeSpec object to a File object. this will always
253
- // return false. do fix.
254
- return typeSpec.match(value, options)
255
- }
256
-
257
- /**
258
- * Checks if a type is valid
259
- *
260
- * @param {string} type - The type to check
261
- * @returns {boolean} Whether the type is valid
262
- */
263
- function isValidType(type) {
264
- return dataTypes.includes(type)
265
- }
266
-
267
- /**
268
- * Checks if a value is of a specified type. Unlike the type function, this
269
- * function does not parse the type string, and only checks for primitive
270
- * or constructor types.
271
- *
272
- * @param {any} value - The value to check
273
- * @param {string} type - The type to check for
274
- * @returns {boolean} Whether the value is of the specified type
275
- */
276
- function isBaseType(value, type) {
277
- if(!isValidType(type))
278
- return false
279
-
280
- const valueType = typeOf(value)
281
-
282
- switch(type.toLowerCase()) {
283
- case "array":
284
- return Array.isArray(value) // Native array check
285
- case "string":
286
- return valueType === "string"
287
- case "boolean":
288
- return valueType === "boolean"
289
- case "number":
290
- return valueType === "number" && !isNaN(value) // Excludes NaN
291
- case "object":
292
- return value !== null && valueType === "object" && !Array.isArray(value) // Excludes arrays and null
293
- case "function":
294
- return valueType === "function"
295
- case "symbol":
296
- return valueType === "symbol" // ES6 Symbol type
297
- case "bigint":
298
- return valueType === "bigint" // BigInt support
299
- case "null":
300
- return value === null // Explicit null check
301
- case "undefined":
302
- return valueType === "undefined" // Explicit undefined check
303
- default:
304
- return false // Unknown type
305
- }
306
- }
307
-
308
- /**
309
- * Returns the type of a value, whether it be a primitive, object, or function.
310
- *
311
- * @param {any} value - The value to check
312
- * @returns {string} The type of the value
313
- */
314
- function typeOf(value) {
315
- return Array.isArray(value) ? "array" : typeof value
316
- }
317
-
318
- /**
319
- * Checks a value is undefined or null.
320
- *
321
- * @param {any} value The value to check
322
- * @returns {boolean} Whether the value is undefined or null
323
- */
324
- function isNothing(value) {
325
- return value === undefined || value === null
326
- }
327
-
328
- /**
329
- * Checks if a value is empty. This function is used to check if an object,
330
- * array, or string is empty. Null and undefined values are considered empty.
331
- *
332
- * @param {any} value The value to check
333
- * @param {boolean} checkForNothing Whether to check for null or undefined
334
- * values
335
- * @returns {boolean} Whether the value is empty
336
- */
337
- function isEmpty(value, checkForNothing = true) {
338
- const type = typeOf(value)
339
-
340
- if(checkForNothing && isNothing(value))
341
- return true
342
-
343
- if(!emptyableTypes.includes(type))
344
- return false
345
-
346
- switch(type) {
347
- case "array":
348
- return value.length === 0
349
- case "object":
350
- return Object.keys(value).length === 0
351
- case "string":
352
- return value.trim().length === 0
353
- default:
354
- return false
355
- }
356
- }
357
-
358
- /**
359
- * Freezes an object and all of its properties recursively.
360
- *
361
- * @param {object} obj The object to freeze.
362
- * @returns {object} The frozen object.
363
- */
364
- function deepFreezeObject(obj) {
365
- if(obj === null || typeof obj !== "object")
366
- return obj // Skip null and non-objects
367
-
368
- // Retrieve and freeze properties
369
- const propNames = Object.getOwnPropertyNames(obj)
370
-
371
- for(const name of propNames) {
372
- const value = obj[name]
373
-
374
- // Recursively freeze nested objects
375
- if(value && typeof value === "object")
376
- deepFreezeObject(value)
377
- }
378
-
379
- // Freeze the object itself
380
- return Object.freeze(obj)
381
- }
382
-
383
- /**
384
- * Validates that a schema matches the expected structure.
385
- *
386
- * TODO get rid of this and all of its uses. We have a new
387
- * contract system now.
388
- *
389
- * @param {object} schema - The schema to validate.
390
- * @param {object} definition - The expected structure.
391
- * @param {Array} stack - The stack trace for nested validation.
392
- * @param {object} logger - The logger to use.
393
- * @returns {boolean} - True if valid, throws an error otherwise.
394
- */
395
- function schemaCompare(schema, definition, stack = [], logger = new Logger()) {
396
- const breadcrumb = key => (stack.length ? `@${stack.join(".")}` : key)
397
- const tag = "[DataUtil.schemaCompare]"
398
- const pad = `${" ".repeat(stack.length * 2)}${stack.length ? "└─ " : ""}`
399
- const debug = (message, key) =>
400
- logger.debug(
401
- `${tag}${pad}${message}${key ? " " + breadcrumb(key) : ""}`,
402
- 2,
403
- true,
404
- )
405
- const error = (message, key) =>
406
- logger.error(`${tag}${pad}${message}${key ? " " + breadcrumb(key) : ""}`)
407
-
408
- const errors = []
409
-
410
- debug(`Keys in schema: ${Object.keys(schema).join(", ")}`)
411
- debug(`Keys in definition: ${Object.keys(definition).join(", ")}`)
412
-
413
- for(const [key, value] of Object.entries(definition)) {
414
- debug(`Checking key: ${key} [required = ${value.required ?? false}]`)
415
-
416
- if(value.required && key in schema === false) {
417
- error(`❌ Required key not found in schema: ${key}`, key)
418
-
419
- errors.push(
420
- new SyntaxError(`Missing required key: ${key} ${breadcrumb(key)}`),
421
- )
422
-
423
- continue
424
- } else {
425
- debug(`✔️ Required key found in schema: ${key}`)
426
- }
427
-
428
- if(key in schema) {
429
- const expectedType = value.dataType
430
- const actualType = schema[key]
431
-
432
- if(!expectedType.match(actualType))
433
- errors.push(
434
- new TypeError(
435
- `Type mismatch for key: ${key}. Expected: ${expectedType}, got: ${actualType} ${breadcrumb(key)}`,
436
- ),
437
- )
438
-
439
- // Recursive validation for nested objects
440
- if(value.contains) {
441
- debug(`Recursing into nested object: ${key}`)
442
- const nestedResult = schemaCompare(
443
- schema[key]?.contains,
444
- value.contains,
445
- [...stack, key],
446
- logger,
447
- )
448
-
449
- if(nestedResult.errors.length)
450
- errors.push(...nestedResult.errors)
451
- }
452
- }
453
- }
454
-
455
- return {status: errors.length === 0 ? "success" : "error", errors}
456
- }
457
-
458
- /**
459
- * Determine the Levenshtein distance between two string values
460
- *
461
- * @param {string} a The first value for comparison.
462
- * @param {string} b The second value for comparison.
463
- * @returns {number} The Levenshtein distance
464
- */
465
- function levenshteinDistance(a, b) {
466
- const matrix = Array.from({length: a.length + 1}, (_, i) =>
467
- Array.from({length: b.length + 1}, (_, j) =>
468
- (i === 0 ? j : j === 0 ? i : 0)
469
- )
470
- )
471
-
472
- for(let i = 1; i <= a.length; i++) {
473
- for(let j = 1; j <= b.length; j++) {
474
- matrix[i][j] =
475
- a[i - 1] === b[j - 1]
476
- ? matrix[i - 1][j - 1]
477
- : 1 + Math.min(
478
- matrix[i - 1][j], matrix[i][j - 1],
479
- matrix[i - 1][j - 1]
480
- )
481
- }
482
- }
483
-
484
- return matrix[a.length][b.length]
485
- }
486
-
487
- /**
488
- * Determine the closest match between a string and allowed values
489
- * from the Levenshtein distance.
490
- *
491
- * @param {string} input The input string to resolve
492
- * @param {Array<string>} allowedValues The values which are permitted
493
- * @returns {string} Suggested, probable match.
494
- */
495
- function findClosestMatch(input, allowedValues) {
496
- const threshold = 2 // Max edit distance for a "close match"
497
- let closestMatch = null
498
- let closestDistance = Infinity
499
-
500
- for(const value of allowedValues) {
501
- const distance = levenshteinDistance(input, value)
502
- if(distance < closestDistance && distance <= threshold) {
503
- closestMatch = value
504
- closestDistance = distance
505
- }
506
- }
507
-
508
- return closestMatch
509
- }
510
-
511
-
512
- export {
513
- // Classes
514
- TypeSpec,
515
- // Variables
516
- dataTypes,
517
- emptyableTypes,
518
- // Functions
519
- allocateObject,
520
- appendString,
521
- arrayIntersection,
522
- arrayPad,
523
- cloneObject,
524
- deepFreezeObject,
525
- findClosestMatch,
526
- isArrayUniform,
527
- isArrayUnique,
528
- isBaseType,
529
- isEmpty,
530
- isNothing,
531
- isObjectEmpty,
532
- isType,
533
- isValidType,
534
- levenshteinDistance,
535
- mapObject,
536
- newTypeSpec,
537
- prependString,
538
- schemaCompare,
539
- typeOf,
540
- }