@gesslar/toolkit 0.8.0 → 1.0.2

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 (55) hide show
  1. package/README.md +39 -0
  2. package/package.json +22 -8
  3. package/src/browser/index.js +10 -0
  4. package/src/browser/lib/Sass.js +168 -0
  5. package/src/browser/lib/Tantrum.js +115 -0
  6. package/src/browser/lib/Util.js +257 -0
  7. package/src/browser/lib/Valid.js +76 -0
  8. package/src/index.js +14 -12
  9. package/src/lib/Cache.js +2 -3
  10. package/src/lib/Contract.js +3 -4
  11. package/src/lib/FS.js +15 -20
  12. package/src/lib/FileObject.js +1 -1
  13. package/src/lib/Glog.js +2 -2
  14. package/src/lib/Sass.js +2 -91
  15. package/src/lib/Schemer.js +2 -2
  16. package/src/lib/Tantrum.js +3 -70
  17. package/src/lib/Terms.js +2 -3
  18. package/src/lib/Util.js +2 -252
  19. package/src/lib/Valid.js +17 -20
  20. package/src/types/browser/index.d.ts +8 -0
  21. package/src/types/browser/index.d.ts.map +1 -0
  22. package/src/types/browser/lib/Collection.d.ts +246 -0
  23. package/src/types/browser/lib/Collection.d.ts.map +1 -0
  24. package/src/types/browser/lib/Data.d.ts +206 -0
  25. package/src/types/browser/lib/Data.d.ts.map +1 -0
  26. package/src/types/browser/lib/Sass.d.ts +62 -0
  27. package/src/types/browser/lib/Sass.d.ts.map +1 -0
  28. package/src/types/browser/lib/Tantrum.d.ts +51 -0
  29. package/src/types/browser/lib/Tantrum.d.ts.map +1 -0
  30. package/src/types/browser/lib/TypeSpec.d.ts +92 -0
  31. package/src/types/browser/lib/TypeSpec.d.ts.map +1 -0
  32. package/src/types/browser/lib/Util.d.ts +129 -0
  33. package/src/types/browser/lib/Util.d.ts.map +1 -0
  34. package/src/types/browser/lib/Valid.d.ts +33 -0
  35. package/src/types/browser/lib/Valid.d.ts.map +1 -0
  36. package/src/types/index.d.ts +10 -10
  37. package/src/types/lib/Cache.d.ts +2 -3
  38. package/src/types/lib/Cache.d.ts.map +1 -1
  39. package/src/types/lib/Contract.d.ts +3 -4
  40. package/src/types/lib/Contract.d.ts.map +1 -1
  41. package/src/types/lib/FS.d.ts +2 -2
  42. package/src/types/lib/FS.d.ts.map +1 -1
  43. package/src/types/lib/Sass.d.ts +2 -55
  44. package/src/types/lib/Sass.d.ts.map +1 -1
  45. package/src/types/lib/Tantrum.d.ts +3 -44
  46. package/src/types/lib/Tantrum.d.ts.map +1 -1
  47. package/src/types/lib/Terms.d.ts +2 -3
  48. package/src/types/lib/Terms.d.ts.map +1 -1
  49. package/src/types/lib/Util.d.ts +2 -123
  50. package/src/types/lib/Util.d.ts.map +1 -1
  51. package/src/types/lib/Valid.d.ts +1 -1
  52. package/src/types/lib/Valid.d.ts.map +1 -1
  53. /package/src/{lib → browser/lib}/Collection.js +0 -0
  54. /package/src/{lib → browser/lib}/Data.js +0 -0
  55. /package/src/{lib → browser/lib}/TypeSpec.js +0 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @file Valid.js
3
+ *
4
+ * Provides validation utilities for type checking and assertion.
5
+ * Includes prototype pollution protection for secure object manipulation.
6
+ */
7
+
8
+ import Sass from "./Sass.js"
9
+ import Data from "./Data.js"
10
+ import Collection from "./Collection.js"
11
+
12
+ /**
13
+ * Validation utility class providing type checking and assertion methods.
14
+ */
15
+ export default class Valid {
16
+ /**
17
+ * Validates a value against a type. Uses Data.isType.
18
+ *
19
+ * @param {unknown} value - The value to validate
20
+ * @param {string} type - The expected type in the form of "object", "object[]", "object|object[]"
21
+ * @param {object} [options] - Additional options for validation.
22
+ */
23
+ static type(value, type, options) {
24
+ Valid.assert(
25
+ Data.isType(value, type, options),
26
+ `Invalid type. Expected ${type}, got ${JSON.stringify(value)}`,
27
+ 1,
28
+ )
29
+ }
30
+
31
+ /**
32
+ * Asserts a condition
33
+ *
34
+ * @param {boolean} condition - The condition to assert
35
+ * @param {string} message - The message to display if the condition is not
36
+ * met
37
+ * @param {number} [arg] - The argument to display if the condition is not
38
+ * met (optional)
39
+ */
40
+ static assert(condition, message, arg = null) {
41
+ if(!Data.isType(condition, "boolean")) {
42
+ throw Sass.new(`Condition must be a boolean, got ${condition}`)
43
+ }
44
+
45
+ if(!Data.isType(message, "string")) {
46
+ throw Sass.new(`Message must be a string, got ${message}`)
47
+ }
48
+
49
+ if(!(arg === null || arg === undefined || typeof arg === "number")) {
50
+ throw Sass.new(`Arg must be a number, got ${arg}`)
51
+ }
52
+
53
+ if(!condition)
54
+ throw Sass.new(`${message}${arg ? `: ${arg}` : ""}`)
55
+ }
56
+
57
+ static #restrictedProto = ["__proto__", "constructor", "prototype"]
58
+
59
+ /**
60
+ * Protects against prototype pollution by checking keys for dangerous property names.
61
+ * Throws if any restricted prototype properties are found in the keys array.
62
+ *
63
+ * @param {Array<string>} keys - Array of property keys to validate
64
+ * @throws {Sass} If any key matches restricted prototype properties (__proto__, constructor, prototype)
65
+ */
66
+ static prototypePollutionProtection(keys) {
67
+ Valid.type(keys, "String[]")
68
+
69
+ const oopsIDidItAgain = Collection.intersection(this.#restrictedProto, keys)
70
+
71
+ Valid.assert(
72
+ oopsIDidItAgain.length === 0,
73
+ `We don't pee in your pool, don't pollute ours with your ${String(oopsIDidItAgain)}`
74
+ )
75
+ }
76
+ }
package/src/index.js CHANGED
@@ -1,19 +1,21 @@
1
- // Core file system abstractions
2
- export {default as FileObject} from "./lib/FileObject.js"
3
- export {default as DirectoryObject} from "./lib/DirectoryObject.js"
4
- export {default as FS} from "./lib/FS.js"
1
+ // Browser-compatible utilities (pure JS)
2
+ export {default as Collection} from "./browser/lib/Collection.js"
3
+ export {default as Data} from "./browser/lib/Data.js"
4
+ export {default as Type} from "./browser/lib/TypeSpec.js"
5
+ export {default as Valid} from "./lib/Valid.js"
6
+
7
+ // Node-enhanced versions (use Term for better formatting, crypto, etc.)
8
+ export {default as Sass} from "./lib/Sass.js"
9
+ export {default as Tantrum} from "./lib/Tantrum.js"
10
+ export {default as Util} from "./lib/Util.js"
5
11
 
6
- // Utility classes
12
+ // Node-specific exports
7
13
  export {default as Cache} from "./lib/Cache.js"
8
- export {default as Collection} from "./lib/Collection.js"
9
14
  export {default as Contract} from "./lib/Contract.js"
10
- export {default as Data} from "./lib/Data.js"
15
+ export {default as DirectoryObject} from "./lib/DirectoryObject.js"
16
+ export {default as FileObject} from "./lib/FileObject.js"
17
+ export {default as FS} from "./lib/FS.js"
11
18
  export {default as Glog} from "./lib/Glog.js"
12
- export {default as Sass} from "./lib/Sass.js"
13
19
  export {default as Schemer} from "./lib/Schemer.js"
14
- export {default as Tantrum} from "./lib/Tantrum.js"
15
20
  export {default as Term} from "./lib/Term.js"
16
21
  export {default as Terms} from "./lib/Terms.js"
17
- export {default as Type} from "./lib/TypeSpec.js"
18
- export {default as Util} from "./lib/Util.js"
19
- export {default as Valid} from "./lib/Valid.js"
package/src/lib/Cache.js CHANGED
@@ -1,4 +1,3 @@
1
- import FileObject from "./FileObject.js"
2
1
  import Sass from "./Sass.js"
3
2
 
4
3
  /**
@@ -22,7 +21,7 @@ export default class Cache {
22
21
  * maintained.
23
22
  *
24
23
  * @private
25
- * @param {FileObject} file - The file object to remove from cache
24
+ * @param {import("./FileObject.js").FileObject} file - The file object to remove from cache
26
25
  * @returns {void}
27
26
  */
28
27
  #cleanup(file) {
@@ -39,7 +38,7 @@ export default class Cache {
39
38
  * freshness while optimizing performance for repeated file access during
40
39
  * parallel processing.
41
40
  *
42
- * @param {FileObject} fileObject - The file object to load and cache
41
+ * @param {import("./FileObject.js").FileObject} fileObject - The file object to load and cache
43
42
  * @returns {Promise<unknown>} The parsed file data (JSON5 or YAML)
44
43
  * @throws {Sass} If the file cannot be found or accessed
45
44
  */
@@ -1,7 +1,6 @@
1
1
  import Sass from "./Sass.js"
2
2
  import Schemer from "./Schemer.js"
3
- import Terms from "./Terms.js"
4
- import Data from "./Data.js"
3
+ import Data from "../browser/lib/Data.js"
5
4
 
6
5
  /**
7
6
  * Contract represents a successful negotiation between Terms.
@@ -18,8 +17,8 @@ export default class Contract {
18
17
  /**
19
18
  * Creates a contract by negotiating between provider and consumer terms
20
19
  *
21
- * @param {Terms} providerTerms - What the provider offers
22
- * @param {Terms} consumerTerms - What the consumer expects
20
+ * @param {import("./Terms.js").Terms} providerTerms - What the provider offers
21
+ * @param {import("./Terms.js").Terms} consumerTerms - What the consumer expects
23
22
  * @param {object} options - Configuration options
24
23
  * @param {import('../types.js').DebugFunction} [options.debug] - Debug function
25
24
  */
package/src/lib/FS.js CHANGED
@@ -9,12 +9,13 @@ import {globby} from "globby"
9
9
  import path from "node:path"
10
10
  import url from "node:url"
11
11
 
12
- import Collection from "./Collection.js"
13
- import DirectoryObject from "./DirectoryObject.js"
14
- import FileObject from "./FileObject.js"
12
+ import Collection from "../browser/lib/Collection.js"
15
13
  import Sass from "./Sass.js"
16
14
  import Valid from "./Valid.js"
17
15
 
16
+ /** @typedef {import("./FileObject.js").default} FileObject */
17
+ /** @typedef {import("./DirectoryObject.js").default} DirectoryObject */
18
+
18
19
  const fdTypes = Object.freeze(["file", "directory"])
19
20
  const upperFdTypes = Object.freeze(fdTypes.map(type => type.toUpperCase()))
20
21
  const fdType = Object.freeze(
@@ -78,37 +79,31 @@ export default class FS {
78
79
  * @throws {Sass} If the glob pattern array is empty or for other search failures.
79
80
  */
80
81
  static async getFiles(glob) {
82
+ const isString = typeof glob === "string"
83
+ const isArray = Array.isArray(glob)
84
+ const isStringArray = isArray && glob.every(item => typeof item === "string")
85
+
81
86
  Valid.assert(
82
- (
83
- (typeof glob === "string" && glob.length > 0) ||
84
- (
85
- Collection.isArrayUniform(glob, "string") &&
86
- glob.length > 0
87
- )
88
- ),
87
+ (isString && glob.length > 0) ||
88
+ (isStringArray && glob.length > 0),
89
89
  "glob must be a non-empty string or array of strings.",
90
90
  1
91
91
  )
92
92
 
93
93
  const globbyArray = (
94
- typeof glob === "string"
95
- ? glob
96
- .split("|")
97
- .map(g => g.trim())
98
- .filter(Boolean)
94
+ isString
95
+ ? glob.split("|").map(g => g.trim()).filter(Boolean)
99
96
  : glob
100
97
  ).map(g => FS.fixSlashes(g))
101
98
 
102
- if(
103
- Array.isArray(globbyArray) &&
104
- Collection.isArrayUniform(globbyArray, "string") &&
105
- !globbyArray.length
106
- )
99
+ if(isArray && !globbyArray.length)
107
100
  throw Sass.new(
108
101
  `Invalid glob pattern: Array cannot be empty. Got ${JSON.stringify(glob)}`,
109
102
  )
110
103
 
111
104
  // Use Globby to fetch matching files
105
+ const {default: FileObject} = await import("./FileObject.js")
106
+
112
107
  const filesArray = await globby(globbyArray)
113
108
  const files = filesArray.map(file => new FileObject(file))
114
109
 
@@ -11,7 +11,7 @@ import util from "node:util"
11
11
  import YAML from "yaml"
12
12
  import {URL} from "node:url"
13
13
 
14
- import Data from "./Data.js"
14
+ import Data from "../browser/lib/Data.js"
15
15
  import DirectoryObject from "./DirectoryObject.js"
16
16
  import FS from "./FS.js"
17
17
  import Sass from "./Sass.js"
package/src/lib/Glog.js CHANGED
@@ -13,9 +13,9 @@
13
13
 
14
14
  import c from "@gesslar/colours"
15
15
 
16
- import Data from "./Data.js"
16
+ import Data from "../browser/lib/Data.js"
17
17
  import Term from "./Term.js"
18
- import Util from "./Util.js"
18
+ import Util from "../browser/lib/Util.js"
19
19
  // ErrorStackParser will be dynamically imported when needed
20
20
 
21
21
  // Enhanced color system using @gesslar/colours
package/src/lib/Sass.js CHANGED
@@ -11,61 +11,14 @@
11
11
  * debugging.
12
12
  */
13
13
 
14
+ import {Sass as BrowserSass} from "../browser/index.js"
14
15
  import Term from "./Term.js"
15
- import Tantrum from "./Tantrum.js"
16
16
 
17
17
  /**
18
18
  * Custom error class for toolkit errors.
19
19
  * Provides error chaining, trace management, and formatted error reporting.
20
20
  */
21
- export default class Sass extends Error {
22
- #trace = []
23
-
24
- /**
25
- * Creates a new Sass instance.
26
- *
27
- * @param {string} message - The error message
28
- * @param {...unknown} [arg] - Additional arguments passed to parent Error constructor
29
- */
30
- constructor(message, ...arg) {
31
- super(message, ...arg)
32
-
33
- this.trace = message
34
- }
35
-
36
- /**
37
- * Gets the error trace array.
38
- *
39
- * @returns {Array<string>} Array of trace messages
40
- */
41
- get trace() {
42
- return this.#trace
43
- }
44
-
45
- /**
46
- * Adds a message to the beginning of the trace array.
47
- *
48
- * @param {string} message - The trace message to add
49
- */
50
- set trace(message) {
51
- this.#trace.unshift(message)
52
- }
53
-
54
- /**
55
- * Adds a trace message and returns this instance for chaining.
56
- *
57
- * @param {string} message - The trace message to add
58
- * @returns {this} This Sass instance for method chaining
59
- */
60
- addTrace(message) {
61
- if(typeof message !== "string")
62
- throw Sass.new(`Sass.addTrace expected string, got ${JSON.stringify(message)}`)
63
-
64
- this.trace = message
65
-
66
- return this
67
- }
68
-
21
+ export default class Sass extends BrowserSass {
69
22
  /**
70
23
  * Reports the error to the terminal with formatted output.
71
24
  * Optionally includes detailed stack trace information.
@@ -124,46 +77,4 @@ export default class Sass extends Error {
124
77
 
125
78
  return lines.join("\n")
126
79
  }
127
-
128
- /**
129
- * Creates an Sass from an existing Error object with additional
130
- * trace message.
131
- *
132
- * @param {Error} error - The original error object
133
- * @param {string} message - Additional trace message to add
134
- * @returns {Sass} New Sass instance with trace from the original error
135
- * @throws {Sass} If the first parameter is not an Error instance
136
- */
137
- static from(error, message) {
138
- if(!(error instanceof Error))
139
- throw Sass.new("Sass.from must take an Error object.")
140
-
141
- const oldMessage = error.message
142
- const newError = new Sass(oldMessage, {cause: error}).addTrace(message)
143
-
144
- return newError
145
- }
146
-
147
- /**
148
- * Factory method to create or enhance Sass instances.
149
- * If error parameter is provided, enhances existing Sass or wraps
150
- * other errors. Otherwise creates a new Sass instance.
151
- *
152
- * @param {string} message - The error message
153
- * @param {Error|Sass|Tantrum} [error] - Optional existing error to wrap or enhance
154
- * @returns {Sass} New or enhanced Sass instance
155
- */
156
- static new(message, error) {
157
- if(error) {
158
- if(error instanceof Tantrum)
159
- return Tantrum.new(message, error)
160
-
161
- return error instanceof Sass
162
- ? error.addTrace(message)
163
- : Sass.from(error, message)
164
- } else {
165
-
166
- return new Sass(message)
167
- }
168
- }
169
80
  }
@@ -1,7 +1,7 @@
1
1
  import Ajv from "ajv"
2
2
 
3
- import Data from "./Data.js"
4
- import Util from "./Util.js"
3
+ import Data from "../browser/lib/Data.js"
4
+ import Util from "../browser/lib/Util.js"
5
5
  import Valid from "./Valid.js"
6
6
 
7
7
  /**
@@ -9,6 +9,7 @@
9
9
  * multiple error scenarios.
10
10
  */
11
11
 
12
+ import {Tantrum as BrowserTantrum} from "../browser/index.js"
12
13
  import Sass from "./Sass.js"
13
14
  import Term from "./Term.js"
14
15
 
@@ -16,63 +17,9 @@ import Term from "./Term.js"
16
17
  * Custom aggregate error class that extends AggregateError.
17
18
  * Automatically wraps plain errors in Sass instances for consistent reporting.
18
19
  */
19
- export default class Tantrum extends AggregateError {
20
- #trace = []
21
-
22
- /**
23
- * Creates a new Tantrum instance.
24
- *
25
- * @param {string} message - The aggregate error message
26
- * @param {Array<Error|Sass>} errors - Array of errors to aggregate
27
- */
20
+ export default class Tantrum extends BrowserTantrum {
28
21
  constructor(message, errors = []) {
29
- // Auto-wrap plain errors in Sass, keep existing Sass instances
30
- const wrappedErrors = errors.map(error => {
31
- if(error instanceof Sass)
32
- return error
33
-
34
- if(!(error instanceof Error))
35
- throw new TypeError(`All items in errors array must be Error instances, got: ${typeof error}`)
36
-
37
- return Sass.new(error.message, error)
38
- })
39
-
40
- super(wrappedErrors, message)
41
- this.name = "Tantrum"
42
- }
43
-
44
- /**
45
- * Adds a trace message and returns this instance for chaining.
46
- *
47
- * @param {string} message - The trace message to add
48
- * @param {Error|Sass} [_error] - Optional error (currently unused, reserved for future use)
49
- * @returns {this} This Tantrum instance for method chaining
50
- */
51
- addTrace(message, _error) {
52
- if(typeof message !== "string")
53
- throw Sass.new(`Tantrum.addTrace expected string, got ${JSON.stringify(message)}`)
54
-
55
- this.trace = message
56
-
57
- return this
58
- }
59
-
60
- /**
61
- * Gets the error trace array.
62
- *
63
- * @returns {Array<string>} Array of trace messages
64
- */
65
- get trace() {
66
- return this.#trace
67
- }
68
-
69
- /**
70
- * Adds a message to the beginning of the trace array.
71
- *
72
- * @param {string} message - The trace message to add
73
- */
74
- set trace(message) {
75
- this.#trace.unshift(message)
22
+ super(message, errors, Sass)
76
23
  }
77
24
 
78
25
  /**
@@ -95,18 +42,4 @@ export default class Tantrum extends AggregateError {
95
42
  error.report(nerdMode)
96
43
  })
97
44
  }
98
-
99
- /**
100
- * Factory method to create a Tantrum instance.
101
- *
102
- * @param {string} message - The aggregate error message
103
- * @param {Array<Error|Sass>} errors - Array of errors to aggregate
104
- * @returns {Tantrum} New Tantrum instance
105
- */
106
- static new(message, errors = []) {
107
- if(errors instanceof Tantrum)
108
- return errors.addTrace(message)
109
-
110
- return new Tantrum(message, errors)
111
- }
112
45
  }
package/src/lib/Terms.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import JSON5 from "json5"
2
2
  import yaml from "yaml"
3
3
 
4
- import Data from "./Data.js"
5
- import DirectoryObject from "./DirectoryObject.js"
4
+ import Data from "../browser/lib/Data.js"
6
5
  import FileObject from "./FileObject.js"
7
6
  import Sass from "./Sass.js"
8
7
  import Valid from "./Valid.js"
@@ -24,7 +23,7 @@ export default class Terms {
24
23
  * Parses terms data, handling file references
25
24
  *
26
25
  * @param {string|object} termsData - Terms data or reference
27
- * @param {DirectoryObject?} directoryObject - Directory context for file resolution
26
+ * @param {import("./DirectoryObject.js").DirectoryObject?} directoryObject - Directory context for file resolution
28
27
  * @returns {object} Parsed terms data
29
28
  */
30
29
  static async parse(termsData, directoryObject) {