@gesslar/toolkit 0.0.1
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/README.md +20 -0
- package/UNLICENSE.txt +24 -0
- package/package.json +62 -0
- package/src/index.js +11 -0
- package/src/lib/Data.js +533 -0
- package/src/lib/DirectoryObject.js +189 -0
- package/src/lib/File.js +346 -0
- package/src/lib/FileObject.js +226 -0
- package/src/lib/Sass.js +166 -0
- package/src/lib/Term.js +171 -0
- package/src/lib/Type.js +207 -0
- package/src/lib/Valid.js +50 -0
- package/src/types/Data.d.ts +96 -0
- package/src/types/DirectoryObject.d.ts +55 -0
- package/src/types/File.d.ts +76 -0
- package/src/types/FileObject.d.ts +59 -0
- package/src/types/Sass.d.ts +23 -0
- package/src/types/Term.d.ts +15 -0
- package/src/types/Type.d.ts +25 -0
- package/src/types/Valid.d.ts +9 -0
- package/src/types/index.d.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @gesslar/toolkit
|
|
2
|
+
|
|
3
|
+
This package is intended to be a collection of useful utilities for any
|
|
4
|
+
project's consumption. Not the kind that gives you bleeding, hacking coughs,
|
|
5
|
+
but the kind that says "yumyum."
|
|
6
|
+
|
|
7
|
+
There are file and directory abstractions, uhmm, there's also some terminal
|
|
8
|
+
things and validity checkers, lots of data functions.
|
|
9
|
+
|
|
10
|
+
Basically, if you want it, it is most definitely here, and working 100% and
|
|
11
|
+
absolutely none of that is true. There are only a few classes here, but they're
|
|
12
|
+
pretty. And if you bug-shame them, I will _come for you like_ ...
|
|
13
|
+
|
|
14
|
+
nah. Just don't be a dick, okay? Play nice, share, lick a veggie and gentlemen,
|
|
15
|
+
spend less than 5 minutes washing your pits, chest, and downstairs and maybe
|
|
16
|
+
give some time to the other parts. Like the parts that walk on things, sit
|
|
17
|
+
on things. Some things that enjoy being sat upon do not enjoy being sat upon
|
|
18
|
+
by gross sitter-upon-things.
|
|
19
|
+
|
|
20
|
+
Also,
|
package/UNLICENSE.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gesslar/toolkit",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Get in, bitches, we're going toolkitting.",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/types/index.d.ts",
|
|
10
|
+
"default": "./src/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"UNLICENSE.txt"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"lint": "eslint src/",
|
|
24
|
+
"submit": "npm publish --access public",
|
|
25
|
+
"update": "npx npm-check-updates -u && npm install",
|
|
26
|
+
"test": "node examples/FileSystem/index.js"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/gesslar/toolkit.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"toolkit",
|
|
34
|
+
"utilities",
|
|
35
|
+
"file",
|
|
36
|
+
"directory",
|
|
37
|
+
"data",
|
|
38
|
+
"flavaflav",
|
|
39
|
+
"chuck",
|
|
40
|
+
"norris",
|
|
41
|
+
"validation",
|
|
42
|
+
"typescript",
|
|
43
|
+
"nodejs",
|
|
44
|
+
"iwasbornonapirateship"
|
|
45
|
+
],
|
|
46
|
+
"author": "gesslar",
|
|
47
|
+
"license": "Unlicense",
|
|
48
|
+
"homepage": "https://github.com/gesslar/toolkit#readme",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"globby": "^14.0.2",
|
|
51
|
+
"json5": "^2.2.3",
|
|
52
|
+
"yaml": "^2.4.5"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@stylistic/eslint-plugin": "^4.4.0",
|
|
56
|
+
"@types/node": "^24.2.1",
|
|
57
|
+
"@typescript-eslint/eslint-plugin": "^8.33.0",
|
|
58
|
+
"@typescript-eslint/parser": "^8.33.0",
|
|
59
|
+
"eslint": "^9.28.0",
|
|
60
|
+
"eslint-plugin-jsdoc": "^50.6.17"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
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 File} from "./lib/File.js"
|
|
5
|
+
|
|
6
|
+
// Utility classes
|
|
7
|
+
export {default as Data} from "./lib/Data.js"
|
|
8
|
+
export {default as Sass} from "./lib/Sass.js"
|
|
9
|
+
export {default as Term} from "./lib/Term.js"
|
|
10
|
+
export {default as Type} from "./lib/Type.js"
|
|
11
|
+
export {default as Valid} from "./lib/Valid.js"
|
package/src/lib/Data.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Data utility functions for type checking, object manipulation, and array operations.
|
|
3
|
+
* Provides comprehensive utilities for working with JavaScript data types and structures.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import TypeSpec from "./Type.js"
|
|
7
|
+
import Sass from "./Sass.js"
|
|
8
|
+
import Valid from "./Valid.js"
|
|
9
|
+
|
|
10
|
+
export default class Data {
|
|
11
|
+
/**
|
|
12
|
+
* Array of JavaScript primitive type names.
|
|
13
|
+
* Includes basic types and object categories from the typeof operator.
|
|
14
|
+
*
|
|
15
|
+
* @type {string[]}
|
|
16
|
+
*/
|
|
17
|
+
static primitives = Object.freeze([
|
|
18
|
+
// Primitives
|
|
19
|
+
"undefined",
|
|
20
|
+
"boolean",
|
|
21
|
+
"number",
|
|
22
|
+
"bigint",
|
|
23
|
+
"string",
|
|
24
|
+
"symbol",
|
|
25
|
+
|
|
26
|
+
// Object Categories from typeof
|
|
27
|
+
"object",
|
|
28
|
+
"function",
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Array of JavaScript constructor names for built-in objects.
|
|
33
|
+
* Includes common object types and typed arrays.
|
|
34
|
+
*
|
|
35
|
+
* @type {string[]}
|
|
36
|
+
*/
|
|
37
|
+
static constructors = Object.freeze([
|
|
38
|
+
// Object Constructors
|
|
39
|
+
"Object",
|
|
40
|
+
"Array",
|
|
41
|
+
"Function",
|
|
42
|
+
"Date",
|
|
43
|
+
"RegExp",
|
|
44
|
+
"Error",
|
|
45
|
+
"Map",
|
|
46
|
+
"Set",
|
|
47
|
+
"WeakMap",
|
|
48
|
+
"WeakSet",
|
|
49
|
+
"Promise",
|
|
50
|
+
"Int8Array",
|
|
51
|
+
"Uint8Array",
|
|
52
|
+
"Float32Array",
|
|
53
|
+
"Float64Array",
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Combined array of all supported data types (primitives and constructors in lowercase).
|
|
58
|
+
* Used for type validation throughout the utility functions.
|
|
59
|
+
*
|
|
60
|
+
* @type {string[]}
|
|
61
|
+
*/
|
|
62
|
+
static dataTypes = Object.freeze([
|
|
63
|
+
...Data.primitives,
|
|
64
|
+
...Data.constructors.map(c => c.toLowerCase())
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Array of type names that can be checked for emptiness.
|
|
69
|
+
* These types have meaningful empty states that can be tested.
|
|
70
|
+
*
|
|
71
|
+
* @type {string[]}
|
|
72
|
+
*/
|
|
73
|
+
static emptyableTypes = Object.freeze(["string", "array", "object"])
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Appends a string to another string if it does not already end with it.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} string - The string to append to
|
|
79
|
+
* @param {string} append - The string to append
|
|
80
|
+
* @returns {string} The appended string
|
|
81
|
+
*/
|
|
82
|
+
static appendString(string, append) {
|
|
83
|
+
return string.endsWith(append) ? string : `${string}${append}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Prepends a string to another string if it does not already start with it.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} string - The string to prepend to
|
|
90
|
+
* @param {string} prepend - The string to prepend
|
|
91
|
+
* @returns {string} The prepended string
|
|
92
|
+
*/
|
|
93
|
+
static prependString(string, prepend) {
|
|
94
|
+
return string.startsWith(prepend) ? string : `${prepend}${string}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks if all elements in an array are of a specified type
|
|
99
|
+
*
|
|
100
|
+
* @param {Array} arr - The array to check
|
|
101
|
+
* @param {string} type - The type to check for (optional, defaults to the
|
|
102
|
+
* type of the first element)
|
|
103
|
+
* @returns {boolean} Whether all elements are of the specified type
|
|
104
|
+
*/
|
|
105
|
+
static isArrayUniform(arr, type) {
|
|
106
|
+
return arr.every(
|
|
107
|
+
(item, _index, arr) => typeof item === (type || typeof arr[0]),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Checks if an array is unique
|
|
113
|
+
*
|
|
114
|
+
* @param {Array} arr - The array of which to remove duplicates
|
|
115
|
+
* @returns {Array} The unique elements of the array
|
|
116
|
+
*/
|
|
117
|
+
static isArrayUnique(arr) {
|
|
118
|
+
return arr.filter((item, index, self) => self.indexOf(item) === index)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Returns the intersection of two arrays.
|
|
123
|
+
*
|
|
124
|
+
* @param {Array} arr1 - The first array.
|
|
125
|
+
* @param {Array} arr2 - The second array.
|
|
126
|
+
* @returns {Array} The intersection of the two arrays.
|
|
127
|
+
*/
|
|
128
|
+
static arrayIntersection(arr1, arr2) {
|
|
129
|
+
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
130
|
+
|
|
131
|
+
return short.filter(value => long.includes(value))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Checks whether two arrays have any elements in common.
|
|
136
|
+
*
|
|
137
|
+
* This function returns `true` if at least one element from `arr1` exists in
|
|
138
|
+
* `arr2`, and `false` otherwise. It optimizes by iterating over the shorter
|
|
139
|
+
* array for efficiency.
|
|
140
|
+
*
|
|
141
|
+
* Example:
|
|
142
|
+
* arrayIntersects([1, 2, 3], [3, 4, 5]) // returns true
|
|
143
|
+
* arrayIntersects(["a", "b"], ["c", "d"]) // returns false
|
|
144
|
+
*
|
|
145
|
+
* @param {Array} arr1 - The first array to check for intersection.
|
|
146
|
+
* @param {Array} arr2 - The second array to check for intersection.
|
|
147
|
+
* @returns {boolean} True if any element is shared between the arrays, false otherwise.
|
|
148
|
+
*/
|
|
149
|
+
static arrayIntersects(arr1, arr2) {
|
|
150
|
+
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
151
|
+
|
|
152
|
+
return !!short.find(value => long.includes(value))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Pads an array to a specified length with a value. This operation
|
|
157
|
+
* occurs in-place.
|
|
158
|
+
*
|
|
159
|
+
* @param {Array} arr - The array to pad.
|
|
160
|
+
* @param {number} length - The length to pad the array to.
|
|
161
|
+
* @param {unknown} value - The value to pad the array with.
|
|
162
|
+
* @param {number} position - The position to pad the array at.
|
|
163
|
+
* @returns {Array} The padded array.
|
|
164
|
+
*/
|
|
165
|
+
static arrayPad(arr, length, value, position = 0) {
|
|
166
|
+
const diff = length - arr.length
|
|
167
|
+
|
|
168
|
+
if(diff <= 0)
|
|
169
|
+
return arr
|
|
170
|
+
|
|
171
|
+
const padding = Array(diff).fill(value)
|
|
172
|
+
|
|
173
|
+
if(position === 0)
|
|
174
|
+
// prepend - default
|
|
175
|
+
return padding.concat(arr)
|
|
176
|
+
else if(position === -1)
|
|
177
|
+
// append
|
|
178
|
+
return arr.concat(padding) // somewhere in the middle - THAT IS ILLEGAL
|
|
179
|
+
else
|
|
180
|
+
throw Sass.new("Invalid position")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clones an object
|
|
185
|
+
*
|
|
186
|
+
* @param {object} obj - The object to clone
|
|
187
|
+
* @param {boolean} freeze - Whether to freeze the cloned object
|
|
188
|
+
* @returns {object} The cloned object
|
|
189
|
+
*/
|
|
190
|
+
static cloneObject(obj, freeze = false) {
|
|
191
|
+
const result = {}
|
|
192
|
+
|
|
193
|
+
for(const [key, value] of Object.entries(obj)) {
|
|
194
|
+
if(Data.isType(value, "object"))
|
|
195
|
+
result[key] = Data.cloneObject(value)
|
|
196
|
+
else
|
|
197
|
+
result[key] = value
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return freeze ? Object.freeze(result) : result
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Allocates an object from a source array and a spec array or function.
|
|
205
|
+
*
|
|
206
|
+
* @param {unknown} source The source array
|
|
207
|
+
* @param {Array|function(unknown): unknown} spec The spec array or function
|
|
208
|
+
* @returns {Promise<object>} The allocated object
|
|
209
|
+
*/
|
|
210
|
+
static async allocateObject(source, spec) {
|
|
211
|
+
// Data
|
|
212
|
+
const workSource = [],
|
|
213
|
+
workSpec = [],
|
|
214
|
+
result = {}
|
|
215
|
+
|
|
216
|
+
if(!Data.isType(source, "array", {allowEmpty: false}))
|
|
217
|
+
throw Sass.new("Source must be an array.")
|
|
218
|
+
|
|
219
|
+
workSource.push(...source)
|
|
220
|
+
|
|
221
|
+
if(
|
|
222
|
+
!Data.isType(spec, "array", {allowEmpty: false}) &&
|
|
223
|
+
!Data.isType(spec, "function")
|
|
224
|
+
)
|
|
225
|
+
throw Sass.new("Spec must be an array or a function.")
|
|
226
|
+
|
|
227
|
+
if(Data.isType(spec, "function")) {
|
|
228
|
+
const specResult = await spec(workSource)
|
|
229
|
+
|
|
230
|
+
if(!Data.isType(specResult, "array"))
|
|
231
|
+
throw Sass.new("Spec resulting from function must be an array.")
|
|
232
|
+
|
|
233
|
+
workSpec.push(...specResult)
|
|
234
|
+
} else if(Data.isType(spec, "array", {allowEmpty: false})) {
|
|
235
|
+
workSpec.push(...spec)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if(workSource.length !== workSpec.length)
|
|
239
|
+
throw Sass.new("Source and spec must have the same number of elements.")
|
|
240
|
+
|
|
241
|
+
// Objects must always be indexed by strings.
|
|
242
|
+
workSource.map((element, index, arr) => (arr[index] = String(element)))
|
|
243
|
+
|
|
244
|
+
// Check that all keys are strings
|
|
245
|
+
if(!Data.isArrayUniform(workSource, "string"))
|
|
246
|
+
throw Sass.new("Indices of an Object must be of type string.")
|
|
247
|
+
|
|
248
|
+
workSource.forEach((element, index) => (result[element] = workSpec[index]))
|
|
249
|
+
|
|
250
|
+
return result
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Maps an object using a transformer function
|
|
255
|
+
*
|
|
256
|
+
* @param {object} original The original object
|
|
257
|
+
* @param {function(unknown): unknown} transformer The transformer function
|
|
258
|
+
* @param {boolean} mutate Whether to mutate the original object
|
|
259
|
+
* @returns {Promise<object>} The mapped object
|
|
260
|
+
*/
|
|
261
|
+
static async mapObject(original, transformer, mutate = false) {
|
|
262
|
+
Valid.validType(original, "object", {allowEmpty: true})
|
|
263
|
+
Valid.validType(transformer, "function")
|
|
264
|
+
Valid.validType(mutate, "boolean")
|
|
265
|
+
|
|
266
|
+
const result = mutate ? original : {}
|
|
267
|
+
|
|
268
|
+
for(const [key, value] of Object.entries(original))
|
|
269
|
+
result[key] = Data.isType(value, "object")
|
|
270
|
+
? await Data.mapObject(value, transformer, mutate)
|
|
271
|
+
: (result[key] = await transformer(key, value))
|
|
272
|
+
|
|
273
|
+
return result
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Checks if an object is empty
|
|
278
|
+
*
|
|
279
|
+
* @param {object} obj - The object to check
|
|
280
|
+
* @returns {boolean} Whether the object is empty
|
|
281
|
+
*/
|
|
282
|
+
static isObjectEmpty(obj) {
|
|
283
|
+
return Object.keys(obj).length === 0
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Creates a type spec from a string. A type spec is an array of objects
|
|
288
|
+
* defining the type of a value and whether an array is expected.
|
|
289
|
+
*
|
|
290
|
+
* @param {string} string - The string to parse into a type spec.
|
|
291
|
+
* @param {object} options - Additional options for parsing.
|
|
292
|
+
* @returns {object[]} An array of type specs.
|
|
293
|
+
*/
|
|
294
|
+
static newTypeSpec(string, options) {
|
|
295
|
+
return new TypeSpec(string, options)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Checks if a value is of a specified type
|
|
300
|
+
*
|
|
301
|
+
* @param {unknown} value The value to check
|
|
302
|
+
* @param {string|TypeSpec} type The type to check for
|
|
303
|
+
* @param {object} options Additional options for checking
|
|
304
|
+
* @returns {boolean} Whether the value is of the specified type
|
|
305
|
+
*/
|
|
306
|
+
static isType(value, type, options = {}) {
|
|
307
|
+
const typeSpec = type instanceof TypeSpec
|
|
308
|
+
? type
|
|
309
|
+
: Data.newTypeSpec(type, options)
|
|
310
|
+
|
|
311
|
+
return typeSpec.match(value, options)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Checks if a type is valid
|
|
316
|
+
*
|
|
317
|
+
* @param {string} type - The type to check
|
|
318
|
+
* @returns {boolean} Whether the type is valid
|
|
319
|
+
*/
|
|
320
|
+
static isValidType(type) {
|
|
321
|
+
return Data.dataTypes.includes(type)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Checks if a value is of a specified type. Unlike the type function, this
|
|
326
|
+
* function does not parse the type string, and only checks for primitive
|
|
327
|
+
* or constructor types.
|
|
328
|
+
*
|
|
329
|
+
* @param {unknown} value - The value to check
|
|
330
|
+
* @param {string} type - The type to check for
|
|
331
|
+
* @returns {boolean} Whether the value is of the specified type
|
|
332
|
+
*/
|
|
333
|
+
static isBaseType(value, type) {
|
|
334
|
+
if(!Data.isValidType(type))
|
|
335
|
+
return false
|
|
336
|
+
|
|
337
|
+
const valueType = Data.typeOf(value)
|
|
338
|
+
|
|
339
|
+
switch(type.toLowerCase()) {
|
|
340
|
+
case "array":
|
|
341
|
+
return Array.isArray(value) // Native array check
|
|
342
|
+
case "string":
|
|
343
|
+
return valueType === "string"
|
|
344
|
+
case "boolean":
|
|
345
|
+
return valueType === "boolean"
|
|
346
|
+
case "number":
|
|
347
|
+
return valueType === "number" && !isNaN(value) // Excludes NaN
|
|
348
|
+
case "object":
|
|
349
|
+
return value !== null && valueType === "object" && !Array.isArray(value) // Excludes arrays and null
|
|
350
|
+
case "function":
|
|
351
|
+
return valueType === "function"
|
|
352
|
+
case "symbol":
|
|
353
|
+
return valueType === "symbol" // ES6 Symbol type
|
|
354
|
+
case "bigint":
|
|
355
|
+
return valueType === "bigint" // BigInt support
|
|
356
|
+
case "null":
|
|
357
|
+
return value === null // Explicit null check
|
|
358
|
+
case "undefined":
|
|
359
|
+
return valueType === "undefined" // Explicit undefined check
|
|
360
|
+
default:
|
|
361
|
+
return false // Unknown type
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns the type of a value, whether it be a primitive, object, or function.
|
|
367
|
+
*
|
|
368
|
+
* @param {unknown} value - The value to check
|
|
369
|
+
* @returns {string} The type of the value
|
|
370
|
+
*/
|
|
371
|
+
static typeOf(value) {
|
|
372
|
+
return Array.isArray(value) ? "array" : typeof value
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Checks a value is undefined or null.
|
|
377
|
+
*
|
|
378
|
+
* @param {unknown} value The value to check
|
|
379
|
+
* @returns {boolean} Whether the value is undefined or null
|
|
380
|
+
*/
|
|
381
|
+
static isNothing(value) {
|
|
382
|
+
return value === undefined || value === null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Checks if a value is empty. This function is used to check if an object,
|
|
387
|
+
* array, or string is empty. Null and undefined values are considered empty.
|
|
388
|
+
*
|
|
389
|
+
* @param {unknown} value The value to check
|
|
390
|
+
* @param {boolean} checkForNothing Whether to check for null or undefined
|
|
391
|
+
* values
|
|
392
|
+
* @returns {boolean} Whether the value is empty
|
|
393
|
+
*/
|
|
394
|
+
static isEmpty(value, checkForNothing = true) {
|
|
395
|
+
const type = Data.typeOf(value)
|
|
396
|
+
|
|
397
|
+
if(checkForNothing && Data.isNothing(value))
|
|
398
|
+
return true
|
|
399
|
+
|
|
400
|
+
if(!Data.emptyableTypes.includes(type))
|
|
401
|
+
return false
|
|
402
|
+
|
|
403
|
+
switch(type) {
|
|
404
|
+
case "array":
|
|
405
|
+
return value.length === 0
|
|
406
|
+
case "object":
|
|
407
|
+
return Object.keys(value).length === 0
|
|
408
|
+
case "string":
|
|
409
|
+
return value.trim().length === 0
|
|
410
|
+
default:
|
|
411
|
+
return false
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Freezes an object and all of its properties recursively.
|
|
417
|
+
*
|
|
418
|
+
* @param {object} obj The object to freeze.
|
|
419
|
+
* @returns {object} The frozen object.
|
|
420
|
+
*/
|
|
421
|
+
static deepFreezeObject(obj) {
|
|
422
|
+
if(obj === null || typeof obj !== "object")
|
|
423
|
+
return obj // Skip null and non-objects
|
|
424
|
+
|
|
425
|
+
// Retrieve and freeze properties
|
|
426
|
+
const propNames = Object.getOwnPropertyNames(obj)
|
|
427
|
+
|
|
428
|
+
for(const name of propNames) {
|
|
429
|
+
const value = obj[name]
|
|
430
|
+
|
|
431
|
+
// Recursively freeze nested objects
|
|
432
|
+
if(typeof value === "object" && value !== null)
|
|
433
|
+
Data.deepFreezeObject(value)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Freeze the object itself
|
|
437
|
+
return Object.freeze(obj)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Ensures that a nested path of objects exists within the given object.
|
|
442
|
+
* Creates empty objects along the path if they don't exist.
|
|
443
|
+
*
|
|
444
|
+
* @param {object} obj - The object to check/modify
|
|
445
|
+
* @param {Array<string>} keys - Array of keys representing the path to ensure
|
|
446
|
+
* @returns {object} Reference to the deepest nested object in the path
|
|
447
|
+
*/
|
|
448
|
+
static assureObjectPath(obj, keys) {
|
|
449
|
+
let current = obj // a moving reference to internal objects within obj
|
|
450
|
+
const len = keys.length
|
|
451
|
+
|
|
452
|
+
for(let i = 0; i < len; i++) {
|
|
453
|
+
const elem = keys[i]
|
|
454
|
+
|
|
455
|
+
if(!current[elem])
|
|
456
|
+
current[elem] = {}
|
|
457
|
+
|
|
458
|
+
current = current[elem]
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Return the current pointer
|
|
462
|
+
return current
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Sets a value in a nested object structure using an array of keys; creating
|
|
467
|
+
* the structure if it does not exist.
|
|
468
|
+
*
|
|
469
|
+
* @param {object} obj - The target object to set the value in
|
|
470
|
+
* @param {Array<string>} keys - Array of keys representing the path to the target property
|
|
471
|
+
* @param {unknown} value - The value to set at the target location
|
|
472
|
+
*/
|
|
473
|
+
static setNestedValue(obj, keys, value) {
|
|
474
|
+
const nested = Data.assureObjectPath(obj, keys.slice(0, -1))
|
|
475
|
+
|
|
476
|
+
nested[keys[keys.length-1]] = value
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Deeply merges two or more objects. Arrays are replaced, not merged.
|
|
481
|
+
*
|
|
482
|
+
* @param {...object} sources - Objects to merge (left to right)
|
|
483
|
+
* @returns {object} The merged object
|
|
484
|
+
*/
|
|
485
|
+
static mergeObject(...sources) {
|
|
486
|
+
const isObject = obj => typeof obj === "object" && obj !== null && !Array.isArray(obj)
|
|
487
|
+
|
|
488
|
+
return sources.reduce((acc, obj) => {
|
|
489
|
+
if(!isObject(obj))
|
|
490
|
+
return acc
|
|
491
|
+
|
|
492
|
+
Object.keys(obj).forEach(key => {
|
|
493
|
+
const accVal = acc[key]
|
|
494
|
+
const objVal = obj[key]
|
|
495
|
+
|
|
496
|
+
if(isObject(accVal) && isObject(objVal))
|
|
497
|
+
acc[key] = Data.mergeObject(accVal, objVal)
|
|
498
|
+
else
|
|
499
|
+
acc[key] = objVal
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
return acc
|
|
503
|
+
}, {})
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Checks if all elements in an array are strings.
|
|
508
|
+
*
|
|
509
|
+
* @param {Array} arr - The array to check.
|
|
510
|
+
* @returns {boolean} Returns true if all elements are strings, false otherwise.
|
|
511
|
+
* @example
|
|
512
|
+
* uniformStringArray(['a', 'b', 'c']) // returns true
|
|
513
|
+
* uniformStringArray(['a', 1, 'c']) // returns false
|
|
514
|
+
*/
|
|
515
|
+
static uniformStringArray(arr) {
|
|
516
|
+
return Array.isArray(arr) && arr.every(item => typeof item === "string")
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Filters an array asynchronously using a predicate function.
|
|
521
|
+
* Applies the predicate to all items in parallel and returns filtered results.
|
|
522
|
+
*
|
|
523
|
+
* @param {Array} arr - The array to filter
|
|
524
|
+
* @param {function(unknown): Promise<boolean>} predicate - Async predicate function that returns a promise resolving to boolean
|
|
525
|
+
* @returns {Promise<Array>} Promise resolving to the filtered array
|
|
526
|
+
*/
|
|
527
|
+
static async asyncFilter(arr, predicate) {
|
|
528
|
+
const results = await Promise.all(arr.map(predicate))
|
|
529
|
+
|
|
530
|
+
return arr.filter((_, index) => results[index])
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
}
|