@gesslar/toolkit 0.2.1 → 0.2.3
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 +4 -4
- package/src/lib/Collection.js +411 -0
- package/src/lib/Data.js +6 -206
- package/src/lib/FS.js +9 -2
- package/src/lib/FileObject.js +2 -1
- package/src/lib/TypeSpec.js +3 -2
- package/src/lib/Util.js +9 -3
- package/src/types/Collection.d.ts +80 -0
- package/src/types/Data.d.ts +0 -112
- package/src/types/Util.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gesslar/toolkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Get in, bitches, we're going toolkitting.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@stylistic/eslint-plugin": "^5.4.0",
|
|
58
58
|
"@types/node": "^24.5.2",
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^8.44.
|
|
60
|
-
"@typescript-eslint/parser": "^8.44.
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
|
60
|
+
"@typescript-eslint/parser": "^8.44.1",
|
|
61
61
|
"eslint": "^9.36.0",
|
|
62
|
-
"eslint-plugin-jsdoc": "^60.1
|
|
62
|
+
"eslint-plugin-jsdoc": "^60.4.1"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/src/lib/Collection.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Data from "./Data.js"
|
|
2
2
|
import Valid from "./Valid.js"
|
|
3
|
+
import Sass from "./Sass.js"
|
|
4
|
+
import Util from "./Util.js"
|
|
3
5
|
|
|
4
6
|
export default class Collection {
|
|
5
7
|
static evalArray(collection, predicate, forward=true) {
|
|
@@ -105,4 +107,413 @@ export default class Collection {
|
|
|
105
107
|
|
|
106
108
|
return unzipped
|
|
107
109
|
}
|
|
110
|
+
|
|
111
|
+
static async asyncMap(array, asyncFn) {
|
|
112
|
+
const req = "Array"
|
|
113
|
+
const type = Data.typeOf(array)
|
|
114
|
+
|
|
115
|
+
Valid.type(array, req, `Invalid array. Expected '${req}', got '${type}'`)
|
|
116
|
+
Valid.type(asyncFn, "Function",
|
|
117
|
+
`Invalid mapper function, expected 'Function', got '${Data.typeOf(asyncFn)}'`)
|
|
118
|
+
|
|
119
|
+
const results = []
|
|
120
|
+
|
|
121
|
+
for(const item of array) {
|
|
122
|
+
results.push(await asyncFn(item))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return results
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Checks if all elements in an array are of a specified type
|
|
130
|
+
*
|
|
131
|
+
* @param {Array} arr - The array to check
|
|
132
|
+
* @param {string} type - The type to check for (optional, defaults to the
|
|
133
|
+
* type of the first element)
|
|
134
|
+
* @returns {boolean} Whether all elements are of the specified type
|
|
135
|
+
*/
|
|
136
|
+
static isArrayUniform(arr, type) {
|
|
137
|
+
const req = "Array"
|
|
138
|
+
const arrType = Data.typeOf(arr)
|
|
139
|
+
|
|
140
|
+
Valid.type(arr, req, `Invalid array. Expected '${req}', got '${arrType}'`)
|
|
141
|
+
|
|
142
|
+
const checkType = type ? Util.capitalize(type) : null
|
|
143
|
+
|
|
144
|
+
return arr.every(
|
|
145
|
+
(item, _index, arr) =>
|
|
146
|
+
Data.typeOf(item) === (checkType || Data.typeOf(arr[0])),
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Checks if an array is unique
|
|
152
|
+
*
|
|
153
|
+
* @param {Array} arr - The array of which to remove duplicates
|
|
154
|
+
* @returns {Array} The unique elements of the array
|
|
155
|
+
*/
|
|
156
|
+
static isArrayUnique(arr) {
|
|
157
|
+
const req = "Array"
|
|
158
|
+
const arrType = Data.typeOf(arr)
|
|
159
|
+
|
|
160
|
+
Valid.type(arr, req, `Invalid array. Expected '${req}', got '${arrType}'`)
|
|
161
|
+
|
|
162
|
+
return arr.filter((item, index, self) => self.indexOf(item) === index)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Returns the intersection of two arrays.
|
|
167
|
+
*
|
|
168
|
+
* @param {Array} arr1 - The first array.
|
|
169
|
+
* @param {Array} arr2 - The second array.
|
|
170
|
+
* @returns {Array} The intersection of the two arrays.
|
|
171
|
+
*/
|
|
172
|
+
static arrayIntersection(arr1, arr2) {
|
|
173
|
+
const req = "Array"
|
|
174
|
+
const arr1Type = Data.typeOf(arr1)
|
|
175
|
+
const arr2Type = Data.typeOf(arr2)
|
|
176
|
+
|
|
177
|
+
Valid.type(arr1, req, `Invalid first array. Expected '${req}', got '${arr1Type}'`)
|
|
178
|
+
Valid.type(arr2, req, `Invalid second array. Expected '${req}', got '${arr2Type}'`)
|
|
179
|
+
|
|
180
|
+
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
181
|
+
|
|
182
|
+
return short.filter(value => long.includes(value))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Checks whether two arrays have any elements in common.
|
|
187
|
+
*
|
|
188
|
+
* This function returns `true` if at least one element from `arr1` exists in
|
|
189
|
+
* `arr2`, and `false` otherwise. It optimizes by iterating over the shorter
|
|
190
|
+
* array for efficiency.
|
|
191
|
+
*
|
|
192
|
+
* Example:
|
|
193
|
+
* arrayIntersects([1, 2, 3], [3, 4, 5]) // returns true
|
|
194
|
+
* arrayIntersects(["a", "b"], ["c", "d"]) // returns false
|
|
195
|
+
*
|
|
196
|
+
* @param {Array} arr1 - The first array to check for intersection.
|
|
197
|
+
* @param {Array} arr2 - The second array to check for intersection.
|
|
198
|
+
* @returns {boolean} True if any element is shared between the arrays, false otherwise.
|
|
199
|
+
*/
|
|
200
|
+
static arrayIntersects(arr1, arr2) {
|
|
201
|
+
const req = "Array"
|
|
202
|
+
const arr1Type = Data.typeOf(arr1)
|
|
203
|
+
const arr2Type = Data.typeOf(arr2)
|
|
204
|
+
|
|
205
|
+
Valid.type(arr1, req, `Invalid first array. Expected '${req}', got '${arr1Type}'`)
|
|
206
|
+
Valid.type(arr2, req, `Invalid second array. Expected '${req}', got '${arr2Type}'`)
|
|
207
|
+
|
|
208
|
+
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
209
|
+
|
|
210
|
+
return !!short.find(value => long.includes(value))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Pads an array to a specified length with a value. This operation
|
|
215
|
+
* occurs in-place.
|
|
216
|
+
*
|
|
217
|
+
* @param {Array} arr - The array to pad.
|
|
218
|
+
* @param {number} length - The length to pad the array to.
|
|
219
|
+
* @param {unknown} value - The value to pad the array with.
|
|
220
|
+
* @param {number} position - The position to pad the array at.
|
|
221
|
+
* @returns {Array} The padded array.
|
|
222
|
+
*/
|
|
223
|
+
static arrayPad(arr, length, value, position = 0) {
|
|
224
|
+
const req = "Array"
|
|
225
|
+
const arrType = Data.typeOf(arr)
|
|
226
|
+
|
|
227
|
+
Valid.type(arr, req, `Invalid array. Expected '${req}', got '${arrType}'`)
|
|
228
|
+
Valid.type(length, "Number", `Invalid length. Expected 'Number', got '${Data.typeOf(length)}'`)
|
|
229
|
+
Valid.type(position, "Number", `Invalid position. Expected 'Number', got '${Data.typeOf(position)}'`)
|
|
230
|
+
|
|
231
|
+
const diff = length - arr.length
|
|
232
|
+
|
|
233
|
+
if(diff <= 0)
|
|
234
|
+
return arr
|
|
235
|
+
|
|
236
|
+
const padding = Array(diff).fill(value)
|
|
237
|
+
|
|
238
|
+
if(position === 0)
|
|
239
|
+
// prepend - default
|
|
240
|
+
return padding.concat(arr)
|
|
241
|
+
else if(position === -1)
|
|
242
|
+
// append
|
|
243
|
+
return arr.concat(padding) // somewhere in the middle - THAT IS ILLEGAL
|
|
244
|
+
else
|
|
245
|
+
throw Sass.new("Invalid position")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Checks if all elements in an array are strings.
|
|
250
|
+
*
|
|
251
|
+
* @param {Array} arr - The array to check.
|
|
252
|
+
* @returns {boolean} Returns true if all elements are strings, false otherwise.
|
|
253
|
+
* @example
|
|
254
|
+
* uniformStringArray(['a', 'b', 'c']) // returns true
|
|
255
|
+
* uniformStringArray(['a', 1, 'c']) // returns false
|
|
256
|
+
*/
|
|
257
|
+
static uniformStringArray(arr) {
|
|
258
|
+
if(!Data.isType(arr, "Array"))
|
|
259
|
+
return false
|
|
260
|
+
|
|
261
|
+
return arr.every(item => typeof item === "string")
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Filters an array asynchronously using a predicate function.
|
|
266
|
+
* Applies the predicate to all items in parallel and returns filtered results.
|
|
267
|
+
*
|
|
268
|
+
* @param {Array} arr - The array to filter
|
|
269
|
+
* @param {function(unknown): Promise<boolean>} predicate - Async predicate function that returns a promise resolving to boolean
|
|
270
|
+
* @returns {Promise<Array>} Promise resolving to the filtered array
|
|
271
|
+
*/
|
|
272
|
+
static async asyncFilter(arr, predicate) {
|
|
273
|
+
const req = "Array"
|
|
274
|
+
const arrType = Data.typeOf(arr)
|
|
275
|
+
|
|
276
|
+
Valid.type(arr, req, `Invalid array. Expected '${req}', got '${arrType}'`)
|
|
277
|
+
Valid.type(predicate, "Function",
|
|
278
|
+
`Invalid predicate function, expected 'Function', got '${Data.typeOf(predicate)}'`)
|
|
279
|
+
|
|
280
|
+
const results = await Promise.all(arr.map(predicate))
|
|
281
|
+
|
|
282
|
+
return arr.filter((_, index) => results[index])
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Clones an object
|
|
287
|
+
*
|
|
288
|
+
* @param {object} obj - The object to clone
|
|
289
|
+
* @param {boolean} freeze - Whether to freeze the cloned object
|
|
290
|
+
* @returns {object} The cloned object
|
|
291
|
+
*/
|
|
292
|
+
static cloneObject(obj, freeze = false) {
|
|
293
|
+
const result = {}
|
|
294
|
+
|
|
295
|
+
for(const [key, value] of Object.entries(obj)) {
|
|
296
|
+
if(Data.isType(value, "Array")) {
|
|
297
|
+
// Clone arrays by mapping over them
|
|
298
|
+
result[key] = value.map(item =>
|
|
299
|
+
Data.isType(item, "object") || Data.isType(item, "Array")
|
|
300
|
+
? Collection.cloneObject(item)
|
|
301
|
+
: item
|
|
302
|
+
)
|
|
303
|
+
} else if(Data.isType(value, "object")) {
|
|
304
|
+
result[key] = Collection.cloneObject(value)
|
|
305
|
+
} else {
|
|
306
|
+
result[key] = value
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return freeze ? Object.freeze(result) : result
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Checks if an object is empty
|
|
315
|
+
*
|
|
316
|
+
* @param {object} obj - The object to check
|
|
317
|
+
* @returns {boolean} Whether the object is empty
|
|
318
|
+
*/
|
|
319
|
+
static isObjectEmpty(obj) {
|
|
320
|
+
const req = "Object"
|
|
321
|
+
const objType = Data.typeOf(obj)
|
|
322
|
+
|
|
323
|
+
Valid.type(obj, req, `Invalid object. Expected '${req}', got '${objType}'`)
|
|
324
|
+
|
|
325
|
+
return Object.keys(obj).length === 0
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Ensures that a nested path of objects exists within the given object.
|
|
330
|
+
* Creates empty objects along the path if they don't exist.
|
|
331
|
+
*
|
|
332
|
+
* @param {object} obj - The object to check/modify
|
|
333
|
+
* @param {Array<string>} keys - Array of keys representing the path to ensure
|
|
334
|
+
* @returns {object} Reference to the deepest nested object in the path
|
|
335
|
+
*/
|
|
336
|
+
static assureObjectPath(obj, keys) {
|
|
337
|
+
const req = "Object"
|
|
338
|
+
const objType = Data.typeOf(obj)
|
|
339
|
+
const keysReq = "Array"
|
|
340
|
+
const keysType = Data.typeOf(keys)
|
|
341
|
+
|
|
342
|
+
Valid.type(obj, req, `Invalid object. Expected '${req}', got '${objType}'`)
|
|
343
|
+
Valid.type(keys, keysReq, `Invalid keys array. Expected '${keysReq}', got '${keysType}'`)
|
|
344
|
+
|
|
345
|
+
let current = obj // a moving reference to internal objects within obj
|
|
346
|
+
const len = keys.length
|
|
347
|
+
|
|
348
|
+
for(let i = 0; i < len; i++) {
|
|
349
|
+
const elem = keys[i]
|
|
350
|
+
|
|
351
|
+
// Prevent prototype pollution
|
|
352
|
+
if(elem === "__proto__" || elem === "constructor" || elem === "prototype") {
|
|
353
|
+
throw Sass.new(`Dangerous key "${elem}" not allowed in object path`)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if(!current[elem])
|
|
357
|
+
current[elem] = {}
|
|
358
|
+
|
|
359
|
+
current = current[elem]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Return the current pointer
|
|
363
|
+
return current
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Sets a value in a nested object structure using an array of keys; creating
|
|
368
|
+
* the structure if it does not exist.
|
|
369
|
+
*
|
|
370
|
+
* @param {object} obj - The target object to set the value in
|
|
371
|
+
* @param {Array<string>} keys - Array of keys representing the path to the target property
|
|
372
|
+
* @param {unknown} value - The value to set at the target location
|
|
373
|
+
*/
|
|
374
|
+
static setNestedValue(obj, keys, value) {
|
|
375
|
+
const req = "Object"
|
|
376
|
+
const objType = Data.typeOf(obj)
|
|
377
|
+
const keysReq = "Array"
|
|
378
|
+
const keysType = Data.typeOf(keys)
|
|
379
|
+
|
|
380
|
+
Valid.type(obj, req, `Invalid object. Expected '${req}', got '${objType}'`)
|
|
381
|
+
Valid.type(keys, keysReq, `Invalid keys array. Expected '${keysReq}', got '${keysType}'`)
|
|
382
|
+
|
|
383
|
+
const nested = Collection.assureObjectPath(obj, keys.slice(0, -1))
|
|
384
|
+
const finalKey = keys[keys.length-1]
|
|
385
|
+
|
|
386
|
+
// Prevent prototype pollution on final key too
|
|
387
|
+
if(finalKey === "__proto__" || finalKey === "constructor" || finalKey === "prototype") {
|
|
388
|
+
throw Sass.new(`Dangerous key "${finalKey}" not allowed in object path`)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
nested[finalKey] = value
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Deeply merges two or more objects. Arrays are replaced, not merged.
|
|
396
|
+
*
|
|
397
|
+
* @param {...object} sources - Objects to merge (left to right)
|
|
398
|
+
* @returns {object} The merged object
|
|
399
|
+
*/
|
|
400
|
+
static mergeObject(...sources) {
|
|
401
|
+
const isObject = obj => typeof obj === "object" && obj !== null && !Array.isArray(obj)
|
|
402
|
+
|
|
403
|
+
return sources.reduce((acc, obj) => {
|
|
404
|
+
if(!isObject(obj))
|
|
405
|
+
return acc
|
|
406
|
+
|
|
407
|
+
Object.keys(obj).forEach(key => {
|
|
408
|
+
const accVal = acc[key]
|
|
409
|
+
const objVal = obj[key]
|
|
410
|
+
|
|
411
|
+
if(isObject(accVal) && isObject(objVal))
|
|
412
|
+
acc[key] = Collection.mergeObject(accVal, objVal)
|
|
413
|
+
else
|
|
414
|
+
acc[key] = objVal
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
return acc
|
|
418
|
+
}, {})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Freezes an object and all of its properties recursively.
|
|
423
|
+
*
|
|
424
|
+
* @param {object} obj The object to freeze.
|
|
425
|
+
* @returns {object} The frozen object.
|
|
426
|
+
*/
|
|
427
|
+
static deepFreezeObject(obj) {
|
|
428
|
+
if(obj === null || typeof obj !== "object")
|
|
429
|
+
return obj // Skip null and non-objects
|
|
430
|
+
|
|
431
|
+
// Retrieve and freeze properties
|
|
432
|
+
const propNames = Object.getOwnPropertyNames(obj)
|
|
433
|
+
|
|
434
|
+
for(const name of propNames) {
|
|
435
|
+
const value = obj[name]
|
|
436
|
+
|
|
437
|
+
// Recursively freeze nested objects
|
|
438
|
+
if(typeof value === "object" && value !== null)
|
|
439
|
+
Collection.deepFreezeObject(value)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Freeze the object itself
|
|
443
|
+
return Object.freeze(obj)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Maps an object using a transformer function
|
|
448
|
+
*
|
|
449
|
+
* @param {object} original The original object
|
|
450
|
+
* @param {function(unknown): unknown} transformer The transformer function
|
|
451
|
+
* @param {boolean} mutate Whether to mutate the original object
|
|
452
|
+
* @returns {Promise<object>} The mapped object
|
|
453
|
+
*/
|
|
454
|
+
static async mapObject(original, transformer, mutate = false) {
|
|
455
|
+
Valid.type(original, "object", {allowEmpty: true})
|
|
456
|
+
Valid.type(transformer, "function")
|
|
457
|
+
Valid.type(mutate, "boolean")
|
|
458
|
+
|
|
459
|
+
const result = mutate ? original : {}
|
|
460
|
+
|
|
461
|
+
for(const [key, value] of Object.entries(original))
|
|
462
|
+
result[key] = Data.isType(value, "object")
|
|
463
|
+
? await Collection.mapObject(value, transformer, mutate)
|
|
464
|
+
: (result[key] = await transformer(key, value))
|
|
465
|
+
|
|
466
|
+
return result
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Allocates an object from a source array and a spec array or function.
|
|
471
|
+
*
|
|
472
|
+
* @param {unknown} source The source array
|
|
473
|
+
* @param {Array<unknown>|function(Array<unknown>): Promise<Array<unknown>>|Array<unknown>} spec The spec array or function
|
|
474
|
+
* @returns {Promise<object>} The allocated object
|
|
475
|
+
*/
|
|
476
|
+
static async allocateObject(source, spec) {
|
|
477
|
+
// Data
|
|
478
|
+
const workSource = [],
|
|
479
|
+
workSpec = [],
|
|
480
|
+
result = {}
|
|
481
|
+
|
|
482
|
+
if(!Data.isType(source, "Array", {allowEmpty: false}))
|
|
483
|
+
throw Sass.new("Source must be an array.")
|
|
484
|
+
|
|
485
|
+
workSource.push(...source)
|
|
486
|
+
|
|
487
|
+
if(
|
|
488
|
+
!Data.isType(spec, "Array", {allowEmpty: false}) &&
|
|
489
|
+
!Data.isType(spec, "function")
|
|
490
|
+
)
|
|
491
|
+
throw Sass.new("Spec must be an array or a function.")
|
|
492
|
+
|
|
493
|
+
if(Data.isType(spec, "Function")) {
|
|
494
|
+
const specResult = await spec(workSource)
|
|
495
|
+
|
|
496
|
+
if(!Data.isType(specResult, "Array"))
|
|
497
|
+
throw Sass.new("Spec resulting from function must be an array.")
|
|
498
|
+
|
|
499
|
+
workSpec.push(...specResult)
|
|
500
|
+
} else if(Data.isType(spec, "Array", {allowEmpty: false})) {
|
|
501
|
+
workSpec.push(...spec)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if(workSource.length !== workSpec.length)
|
|
505
|
+
throw Sass.new("Source and spec must have the same number of elements.")
|
|
506
|
+
|
|
507
|
+
// Objects must always be indexed by strings.
|
|
508
|
+
workSource.map((element, index, arr) => (arr[index] = String(element)))
|
|
509
|
+
|
|
510
|
+
// Check that all keys are strings
|
|
511
|
+
if(!Collection.isArrayUniform(workSource, "String"))
|
|
512
|
+
throw Sass.new("Indices of an Object must be of type string.")
|
|
513
|
+
|
|
514
|
+
workSource.forEach((element, index) => (result[element] = workSpec[index]))
|
|
515
|
+
|
|
516
|
+
return result
|
|
517
|
+
}
|
|
518
|
+
|
|
108
519
|
}
|
package/src/lib/Data.js
CHANGED
|
@@ -6,10 +6,7 @@
|
|
|
6
6
|
* structures.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import Sass from "./Sass.js"
|
|
10
9
|
import TypeSpec from "./TypeSpec.js"
|
|
11
|
-
import Util from "./Util.js"
|
|
12
|
-
import Valid from "./Valid.js"
|
|
13
10
|
|
|
14
11
|
export default class Data {
|
|
15
12
|
/**
|
|
@@ -101,206 +98,6 @@ export default class Data {
|
|
|
101
98
|
return string.startsWith(prepend) ? string : `${prepend}${string}`
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
/**
|
|
105
|
-
* Checks if all elements in an array are of a specified type
|
|
106
|
-
*
|
|
107
|
-
* @param {Array} arr - The array to check
|
|
108
|
-
* @param {string} type - The type to check for (optional, defaults to the
|
|
109
|
-
* type of the first element)
|
|
110
|
-
* @returns {boolean} Whether all elements are of the specified type
|
|
111
|
-
*/
|
|
112
|
-
static isArrayUniform(arr, type) {
|
|
113
|
-
const checkType = type ? Util.capitalize(type) : null
|
|
114
|
-
|
|
115
|
-
return arr.every(
|
|
116
|
-
(item, _index, arr) =>
|
|
117
|
-
Data.typeOf(item) === (checkType || Data.typeOf(arr[0])),
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Checks if an array is unique
|
|
123
|
-
*
|
|
124
|
-
* @param {Array} arr - The array of which to remove duplicates
|
|
125
|
-
* @returns {Array} The unique elements of the array
|
|
126
|
-
*/
|
|
127
|
-
static isArrayUnique(arr) {
|
|
128
|
-
return arr.filter((item, index, self) => self.indexOf(item) === index)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Returns the intersection of two arrays.
|
|
133
|
-
*
|
|
134
|
-
* @param {Array} arr1 - The first array.
|
|
135
|
-
* @param {Array} arr2 - The second array.
|
|
136
|
-
* @returns {Array} The intersection of the two arrays.
|
|
137
|
-
*/
|
|
138
|
-
static arrayIntersection(arr1, arr2) {
|
|
139
|
-
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
140
|
-
|
|
141
|
-
return short.filter(value => long.includes(value))
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Checks whether two arrays have any elements in common.
|
|
146
|
-
*
|
|
147
|
-
* This function returns `true` if at least one element from `arr1` exists in
|
|
148
|
-
* `arr2`, and `false` otherwise. It optimizes by iterating over the shorter
|
|
149
|
-
* array for efficiency.
|
|
150
|
-
*
|
|
151
|
-
* Example:
|
|
152
|
-
* arrayIntersects([1, 2, 3], [3, 4, 5]) // returns true
|
|
153
|
-
* arrayIntersects(["a", "b"], ["c", "d"]) // returns false
|
|
154
|
-
*
|
|
155
|
-
* @param {Array} arr1 - The first array to check for intersection.
|
|
156
|
-
* @param {Array} arr2 - The second array to check for intersection.
|
|
157
|
-
* @returns {boolean} True if any element is shared between the arrays, false otherwise.
|
|
158
|
-
*/
|
|
159
|
-
static arrayIntersects(arr1, arr2) {
|
|
160
|
-
const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
|
|
161
|
-
|
|
162
|
-
return !!short.find(value => long.includes(value))
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Pads an array to a specified length with a value. This operation
|
|
167
|
-
* occurs in-place.
|
|
168
|
-
*
|
|
169
|
-
* @param {Array} arr - The array to pad.
|
|
170
|
-
* @param {number} length - The length to pad the array to.
|
|
171
|
-
* @param {unknown} value - The value to pad the array with.
|
|
172
|
-
* @param {number} position - The position to pad the array at.
|
|
173
|
-
* @returns {Array} The padded array.
|
|
174
|
-
*/
|
|
175
|
-
static arrayPad(arr, length, value, position = 0) {
|
|
176
|
-
const diff = length - arr.length
|
|
177
|
-
|
|
178
|
-
if(diff <= 0)
|
|
179
|
-
return arr
|
|
180
|
-
|
|
181
|
-
const padding = Array(diff).fill(value)
|
|
182
|
-
|
|
183
|
-
if(position === 0)
|
|
184
|
-
// prepend - default
|
|
185
|
-
return padding.concat(arr)
|
|
186
|
-
else if(position === -1)
|
|
187
|
-
// append
|
|
188
|
-
return arr.concat(padding) // somewhere in the middle - THAT IS ILLEGAL
|
|
189
|
-
else
|
|
190
|
-
throw Sass.new("Invalid position")
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Clones an object
|
|
195
|
-
*
|
|
196
|
-
* @param {object} obj - The object to clone
|
|
197
|
-
* @param {boolean} freeze - Whether to freeze the cloned object
|
|
198
|
-
* @returns {object} The cloned object
|
|
199
|
-
*/
|
|
200
|
-
static cloneObject(obj, freeze = false) {
|
|
201
|
-
const result = {}
|
|
202
|
-
|
|
203
|
-
for(const [key, value] of Object.entries(obj)) {
|
|
204
|
-
if(Data.isType(value, "Array")) {
|
|
205
|
-
// Clone arrays by mapping over them
|
|
206
|
-
result[key] = value.map(item =>
|
|
207
|
-
Data.isType(item, "object") || Data.isType(item, "Array")
|
|
208
|
-
? Data.cloneObject(item)
|
|
209
|
-
: item
|
|
210
|
-
)
|
|
211
|
-
} else if(Data.isType(value, "object")) {
|
|
212
|
-
result[key] = Data.cloneObject(value)
|
|
213
|
-
} else {
|
|
214
|
-
result[key] = value
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return freeze ? Object.freeze(result) : result
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Allocates an object from a source array and a spec array or function.
|
|
223
|
-
*
|
|
224
|
-
* @param {unknown} source The source array
|
|
225
|
-
* @param {Array<unknown>|function(Array<unknown>): Promise<Array<unknown>>|Array<unknown>} spec The spec array or function
|
|
226
|
-
* @returns {Promise<object>} The allocated object
|
|
227
|
-
*/
|
|
228
|
-
static async allocateObject(source, spec) {
|
|
229
|
-
// Data
|
|
230
|
-
const workSource = [],
|
|
231
|
-
workSpec = [],
|
|
232
|
-
result = {}
|
|
233
|
-
|
|
234
|
-
if(!Data.isType(source, "Array", {allowEmpty: false}))
|
|
235
|
-
throw Sass.new("Source must be an array.")
|
|
236
|
-
|
|
237
|
-
workSource.push(...source)
|
|
238
|
-
|
|
239
|
-
if(
|
|
240
|
-
!Data.isType(spec, "Array", {allowEmpty: false}) &&
|
|
241
|
-
!Data.isType(spec, "function")
|
|
242
|
-
)
|
|
243
|
-
throw Sass.new("Spec must be an array or a function.")
|
|
244
|
-
|
|
245
|
-
if(Data.isType(spec, "Function")) {
|
|
246
|
-
const specResult = await spec(workSource)
|
|
247
|
-
|
|
248
|
-
if(!Data.isType(specResult, "Array"))
|
|
249
|
-
throw Sass.new("Spec resulting from function must be an array.")
|
|
250
|
-
|
|
251
|
-
workSpec.push(...specResult)
|
|
252
|
-
} else if(Data.isType(spec, "Array", {allowEmpty: false})) {
|
|
253
|
-
workSpec.push(...spec)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if(workSource.length !== workSpec.length)
|
|
257
|
-
throw Sass.new("Source and spec must have the same number of elements.")
|
|
258
|
-
|
|
259
|
-
// Objects must always be indexed by strings.
|
|
260
|
-
workSource.map((element, index, arr) => (arr[index] = String(element)))
|
|
261
|
-
|
|
262
|
-
// Check that all keys are strings
|
|
263
|
-
if(!Data.isArrayUniform(workSource, "String"))
|
|
264
|
-
throw Sass.new("Indices of an Object must be of type string.")
|
|
265
|
-
|
|
266
|
-
workSource.forEach((element, index) => (result[element] = workSpec[index]))
|
|
267
|
-
|
|
268
|
-
return result
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Maps an object using a transformer function
|
|
273
|
-
*
|
|
274
|
-
* @param {object} original The original object
|
|
275
|
-
* @param {function(unknown): unknown} transformer The transformer function
|
|
276
|
-
* @param {boolean} mutate Whether to mutate the original object
|
|
277
|
-
* @returns {Promise<object>} The mapped object
|
|
278
|
-
*/
|
|
279
|
-
static async mapObject(original, transformer, mutate = false) {
|
|
280
|
-
Valid.type(original, "object", {allowEmpty: true})
|
|
281
|
-
Valid.type(transformer, "function")
|
|
282
|
-
Valid.type(mutate, "boolean")
|
|
283
|
-
|
|
284
|
-
const result = mutate ? original : {}
|
|
285
|
-
|
|
286
|
-
for(const [key, value] of Object.entries(original))
|
|
287
|
-
result[key] = Data.isType(value, "object")
|
|
288
|
-
? await Data.mapObject(value, transformer, mutate)
|
|
289
|
-
: (result[key] = await transformer(key, value))
|
|
290
|
-
|
|
291
|
-
return result
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Checks if an object is empty
|
|
296
|
-
*
|
|
297
|
-
* @param {object} obj - The object to check
|
|
298
|
-
* @returns {boolean} Whether the object is empty
|
|
299
|
-
*/
|
|
300
|
-
static isObjectEmpty(obj) {
|
|
301
|
-
return Object.keys(obj).length === 0
|
|
302
|
-
}
|
|
303
|
-
|
|
304
101
|
/**
|
|
305
102
|
* Creates a type spec from a string. A type spec is an array of objects
|
|
306
103
|
* defining the type of a value and whether an array is expected.
|
|
@@ -381,9 +178,12 @@ export default class Data {
|
|
|
381
178
|
|
|
382
179
|
const type = typeof value
|
|
383
180
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
181
|
+
if(type === "object")
|
|
182
|
+
return value.constructor.name
|
|
183
|
+
|
|
184
|
+
const [first, ...rest] = Array.from(type)
|
|
185
|
+
|
|
186
|
+
return `${first?.toLocaleUpperCase() ?? ""}${rest.join("")}`
|
|
387
187
|
}
|
|
388
188
|
|
|
389
189
|
/**
|
package/src/lib/FS.js
CHANGED
|
@@ -2,6 +2,7 @@ import {globby} from "globby"
|
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import url from "node:url"
|
|
4
4
|
|
|
5
|
+
import Collection from "./Collection.js"
|
|
5
6
|
import Data from "./Data.js"
|
|
6
7
|
import DirectoryObject from "./DirectoryObject.js"
|
|
7
8
|
import FileObject from "./FileObject.js"
|
|
@@ -10,7 +11,9 @@ import Valid from "./Valid.js"
|
|
|
10
11
|
|
|
11
12
|
const fdTypes = Object.freeze(["file", "directory"])
|
|
12
13
|
const upperFdTypes = Object.freeze(fdTypes.map(type => type.toUpperCase()))
|
|
13
|
-
const fdType = Object.freeze(
|
|
14
|
+
const fdType = Object.freeze(
|
|
15
|
+
await Collection.allocateObject(upperFdTypes, fdTypes)
|
|
16
|
+
)
|
|
14
17
|
|
|
15
18
|
export default class FS {
|
|
16
19
|
static fdTypes = fdTypes
|
|
@@ -115,7 +118,11 @@ export default class FS {
|
|
|
115
118
|
* @returns {string} The relative path from `from` to `to`, or the absolute path if not reachable
|
|
116
119
|
*/
|
|
117
120
|
static relativeOrAbsolutePath(from, to) {
|
|
118
|
-
const
|
|
121
|
+
const fromBasePath = from.isDirectory
|
|
122
|
+
? from.path
|
|
123
|
+
: from.directory?.path ?? path.dirname(from.path)
|
|
124
|
+
|
|
125
|
+
const relative = path.relative(fromBasePath, to.path)
|
|
119
126
|
|
|
120
127
|
return relative.startsWith("..")
|
|
121
128
|
? to.path
|
package/src/lib/FileObject.js
CHANGED
|
@@ -385,12 +385,13 @@ export default class FileObject extends FS {
|
|
|
385
385
|
*/
|
|
386
386
|
async loadData(type="any", encoding="utf8") {
|
|
387
387
|
const content = await this.read(encoding)
|
|
388
|
+
const normalizedType = type.toLocaleLowerCase()
|
|
388
389
|
const toTry = {
|
|
389
390
|
json5: [JSON5],
|
|
390
391
|
json: [JSON5],
|
|
391
392
|
yaml: [YAML],
|
|
392
393
|
any: [JSON5,YAML]
|
|
393
|
-
}[
|
|
394
|
+
}[normalizedType]
|
|
394
395
|
|
|
395
396
|
if(!toTry) {
|
|
396
397
|
throw Sass.new(`Unsupported data type '${type}'. Supported types: json, json5, yaml, any`)
|
package/src/lib/TypeSpec.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* including arrays, unions, and options.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import Collection from "./Collection.js"
|
|
8
8
|
import Data from "./Data.js"
|
|
9
|
+
import Sass from "./Sass.js"
|
|
9
10
|
import Util from "./Util.js"
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -180,7 +181,7 @@ export default class TypeSpec {
|
|
|
180
181
|
return allowEmpty
|
|
181
182
|
|
|
182
183
|
// Check if array elements match the required type
|
|
183
|
-
return
|
|
184
|
+
return Collection.isArrayUniform(value, allowedType)
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
return false
|
package/src/lib/Util.js
CHANGED
|
@@ -15,9 +15,15 @@ export default class Util {
|
|
|
15
15
|
* @returns {string} Text with first letter capitalized
|
|
16
16
|
*/
|
|
17
17
|
static capitalize(text) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if(typeof text !== "string")
|
|
19
|
+
throw new TypeError("Util.capitalize expects a string")
|
|
20
|
+
|
|
21
|
+
if(text.length === 0)
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
const [first, ...rest] = Array.from(text)
|
|
25
|
+
|
|
26
|
+
return `${first.toLocaleUpperCase()}${rest.join("")}`
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -135,4 +135,84 @@ export default class Collection {
|
|
|
135
135
|
* ```
|
|
136
136
|
*/
|
|
137
137
|
static unzip<T>(array: T[][]): T[][]
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Maps an array through an async function, executing operations sequentially.
|
|
141
|
+
*
|
|
142
|
+
* Unlike Promise.all(array.map(fn)), this executes each async operation
|
|
143
|
+
* one at a time, maintaining order and preventing overwhelming external resources.
|
|
144
|
+
*
|
|
145
|
+
* @param array - The array to map over
|
|
146
|
+
* @param asyncFn - Async function called for each element: (element) => Promise<result>
|
|
147
|
+
* @returns Promise resolving to array of mapped results
|
|
148
|
+
*
|
|
149
|
+
* @throws {Sass} If array is not an Array or asyncFn is not a Function
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* import { Collection } from '@gesslar/toolkit'
|
|
154
|
+
*
|
|
155
|
+
* // Sequential API calls (won't overwhelm server)
|
|
156
|
+
* const urls = ['url1', 'url2', 'url3']
|
|
157
|
+
* const responses = await Collection.asyncMap(urls, async (url) => {
|
|
158
|
+
* return await fetch(url).then(r => r.json())
|
|
159
|
+
* })
|
|
160
|
+
* console.log(responses) // [data1, data2, data3]
|
|
161
|
+
*
|
|
162
|
+
* // Works with sync functions too
|
|
163
|
+
* const numbers = [1, 2, 3]
|
|
164
|
+
* const doubled = await Collection.asyncMap(numbers, async (n) => n * 2)
|
|
165
|
+
* console.log(doubled) // [2, 4, 6]
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
static asyncMap<T, R>(array: T[], asyncFn: (element: T) => Promise<R> | R): Promise<R[]>
|
|
169
|
+
|
|
170
|
+
/** Check if all elements in an array are of a specified type or all the same type */
|
|
171
|
+
static isArrayUniform(arr: Array<unknown>, type?: string): boolean
|
|
172
|
+
|
|
173
|
+
/** Remove duplicate elements from an array, returning a new array with unique values */
|
|
174
|
+
static isArrayUnique<T>(arr: Array<T>): Array<T>
|
|
175
|
+
|
|
176
|
+
/** Get the intersection of two arrays */
|
|
177
|
+
static arrayIntersection<T>(arr1: Array<T>, arr2: Array<T>): Array<T>
|
|
178
|
+
|
|
179
|
+
/** Check if two arrays have any elements in common */
|
|
180
|
+
static arrayIntersects<T>(arr1: Array<T>, arr2: Array<T>): boolean
|
|
181
|
+
|
|
182
|
+
/** Pad an array to a specified length */
|
|
183
|
+
static arrayPad<T>(arr: Array<T>, length: number, value: T, position?: number): Array<T>
|
|
184
|
+
|
|
185
|
+
/** Check if all elements in an array are strings */
|
|
186
|
+
static uniformStringArray(arr: Array<unknown>): arr is Array<string>
|
|
187
|
+
|
|
188
|
+
/** Filter an array asynchronously */
|
|
189
|
+
static asyncFilter<T>(arr: Array<T>, predicate: (item: T) => Promise<boolean>): Promise<Array<T>>
|
|
190
|
+
|
|
191
|
+
/** Clone an object */
|
|
192
|
+
static cloneObject<T extends Record<string, any>>(obj: T, freeze?: boolean): T
|
|
193
|
+
|
|
194
|
+
/** Check if an object is empty */
|
|
195
|
+
static isObjectEmpty(obj: Record<string, any>): boolean
|
|
196
|
+
|
|
197
|
+
/** Ensure a nested path of objects exists */
|
|
198
|
+
static assureObjectPath(obj: Record<string, any>, keys: Array<string>): Record<string, any>
|
|
199
|
+
|
|
200
|
+
/** Set a value in a nested object structure */
|
|
201
|
+
static setNestedValue(obj: Record<string, any>, keys: Array<string>, value: unknown): void
|
|
202
|
+
|
|
203
|
+
/** Deeply merge objects */
|
|
204
|
+
static mergeObject<T extends Record<string, any>>(...sources: Array<T>): T
|
|
205
|
+
|
|
206
|
+
/** Recursively freeze an object */
|
|
207
|
+
static deepFreezeObject<T>(obj: T): T
|
|
208
|
+
|
|
209
|
+
/** Map an object using a transformer function */
|
|
210
|
+
static mapObject<T extends Record<string, any>, R>(
|
|
211
|
+
original: T,
|
|
212
|
+
transformer: (key: string, value: any) => R | Promise<R>,
|
|
213
|
+
mutate?: boolean
|
|
214
|
+
): Promise<Record<string, R>>
|
|
215
|
+
|
|
216
|
+
/** Allocate an object from a source array and spec */
|
|
217
|
+
static allocateObject(source: Array<unknown>, spec: Array<unknown> | ((source: Array<unknown>) => Promise<Array<unknown>> | Array<unknown>)): Promise<Record<string, unknown>>
|
|
138
218
|
}
|
package/src/types/Data.d.ts
CHANGED
|
@@ -91,118 +91,6 @@ export default class Data {
|
|
|
91
91
|
*/
|
|
92
92
|
static prependString(string: string, prepend: string): string
|
|
93
93
|
|
|
94
|
-
/**
|
|
95
|
-
* Check if all elements in an array are of a specified type or all the same type.
|
|
96
|
-
*
|
|
97
|
-
* Performs type checking on every element in the array using the toolkit's type
|
|
98
|
-
* system. If no type is specified, checks that all elements are of the same type.
|
|
99
|
-
* Useful for validating data structures and ensuring type consistency before processing.
|
|
100
|
-
*
|
|
101
|
-
* @param arr - The array to check for type uniformity. Can be empty (returns true).
|
|
102
|
-
* @param type - Optional type name to check against. If not provided, checks that all
|
|
103
|
-
* elements have the same type. Must be a valid type from Data.dataTypes.
|
|
104
|
-
* @returns True if all elements match the specified type or are all the same type,
|
|
105
|
-
* false if there's any type mismatch or if type parameter is invalid
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```typescript
|
|
109
|
-
* import { Data } from '@gesslar/toolkit'
|
|
110
|
-
*
|
|
111
|
-
* // Check for specific type uniformity
|
|
112
|
-
* const numbers = [1, 2, 3, 4, 5]
|
|
113
|
-
* const strings = ['a', 'b', 'c']
|
|
114
|
-
* const mixed = [1, 'a', true]
|
|
115
|
-
*
|
|
116
|
-
* console.log(Data.isArrayUniform(numbers, 'number')) // true
|
|
117
|
-
* console.log(Data.isArrayUniform(strings, 'string')) // true
|
|
118
|
-
* console.log(Data.isArrayUniform(mixed, 'number')) // false
|
|
119
|
-
*
|
|
120
|
-
* // Check that all elements are the same type (any type)
|
|
121
|
-
* console.log(Data.isArrayUniform(numbers)) // true (all numbers)
|
|
122
|
-
* console.log(Data.isArrayUniform(strings)) // true (all strings)
|
|
123
|
-
* console.log(Data.isArrayUniform(mixed)) // false (mixed types)
|
|
124
|
-
* console.log(Data.isArrayUniform([])) // true (empty array)
|
|
125
|
-
*
|
|
126
|
-
* // Useful for validation before processing
|
|
127
|
-
* function processNumbers(data: unknown[]) {
|
|
128
|
-
* if (!Data.isArrayUniform(data, 'number')) {
|
|
129
|
-
* throw new Error('Array must contain only numbers')
|
|
130
|
-
* }
|
|
131
|
-
* return data.reduce((sum, num) => sum + num, 0)
|
|
132
|
-
* }
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
|
-
static isArrayUniform(arr: Array<unknown>, type?: string): boolean
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Remove duplicate elements from an array, returning a new array with unique values.
|
|
139
|
-
*
|
|
140
|
-
* Creates a new array containing only the first occurrence of each unique value,
|
|
141
|
-
* preserving the original order of first appearances. Uses strict equality (===)
|
|
142
|
-
* for primitive comparisons and shallow comparison for objects.
|
|
143
|
-
*
|
|
144
|
-
* @param arr - The array to remove duplicates from. Can be empty or contain any types.
|
|
145
|
-
* @returns A new array with duplicate elements removed, preserving order of first occurrence
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```typescript
|
|
149
|
-
* import { Data } from '@gesslar/toolkit'
|
|
150
|
-
*
|
|
151
|
-
* // Basic duplicate removal
|
|
152
|
-
* const numbers = [1, 2, 2, 3, 3, 4]
|
|
153
|
-
* const uniqueNumbers = Data.isArrayUnique(numbers)
|
|
154
|
-
* console.log(uniqueNumbers) // [1, 2, 3, 4]
|
|
155
|
-
*
|
|
156
|
-
* // Mixed types
|
|
157
|
-
* const mixed = ['a', 1, 'a', 2, 1, 'b']
|
|
158
|
-
* const uniqueMixed = Data.isArrayUnique(mixed)
|
|
159
|
-
* console.log(uniqueMixed) // ['a', 1, 2, 'b']
|
|
160
|
-
*
|
|
161
|
-
* // Object arrays (shallow comparison)
|
|
162
|
-
* const users = [
|
|
163
|
-
* { id: 1, name: 'Alice' },
|
|
164
|
-
* { id: 2, name: 'Bob' },
|
|
165
|
-
* { id: 1, name: 'Alice' } // Different object reference, not filtered
|
|
166
|
-
* ]
|
|
167
|
-
* const uniqueUsers = Data.isArrayUnique(users)
|
|
168
|
-
* console.log(uniqueUsers.length) // 3 (objects compared by reference)
|
|
169
|
-
*
|
|
170
|
-
* // Empty and single element arrays
|
|
171
|
-
* console.log(Data.isArrayUnique([])) // []
|
|
172
|
-
* console.log(Data.isArrayUnique(['single'])) // ['single']
|
|
173
|
-
*
|
|
174
|
-
* // String array deduplication
|
|
175
|
-
* const tags = ['javascript', 'node', 'javascript', 'typescript', 'node']
|
|
176
|
-
* const uniqueTags = Data.isArrayUnique(tags)
|
|
177
|
-
* console.log(uniqueTags) // ['javascript', 'node', 'typescript']
|
|
178
|
-
* ```
|
|
179
|
-
*/
|
|
180
|
-
static isArrayUnique<T>(arr: Array<T>): Array<T>
|
|
181
|
-
|
|
182
|
-
/** Get the intersection of two arrays */
|
|
183
|
-
static arrayIntersection<T>(arr1: Array<T>, arr2: Array<T>): Array<T>
|
|
184
|
-
|
|
185
|
-
/** Check if two arrays have any elements in common */
|
|
186
|
-
static arrayIntersects<T>(arr1: Array<T>, arr2: Array<T>): boolean
|
|
187
|
-
|
|
188
|
-
/** Pad an array to a specified length */
|
|
189
|
-
static arrayPad<T>(arr: Array<T>, length: number, value: T, position?: number): Array<T>
|
|
190
|
-
|
|
191
|
-
/** Clone an object */
|
|
192
|
-
static cloneObject<T extends Record<string, any>>(obj: T, freeze?: boolean): T
|
|
193
|
-
|
|
194
|
-
/** Allocate an object from a source array and spec */
|
|
195
|
-
static allocateObject(source: Array<unknown>, spec: Array<unknown> | ((source: Array<unknown>) => Promise<Array<unknown>> | Array<unknown>)): Promise<Record<string, unknown>>
|
|
196
|
-
|
|
197
|
-
/** Map an object using a transformer function */
|
|
198
|
-
static mapObject<T extends Record<string, any>, R>(
|
|
199
|
-
original: T,
|
|
200
|
-
transformer: (key: string, value: any) => R | Promise<R>,
|
|
201
|
-
mutate?: boolean
|
|
202
|
-
): Promise<Record<string, R>>
|
|
203
|
-
|
|
204
|
-
/** Check if an object is empty */
|
|
205
|
-
static isObjectEmpty(obj: Record<string, any>): boolean
|
|
206
94
|
|
|
207
95
|
/** Create a type spec from a string */
|
|
208
96
|
static newTypeSpec(string: string, options?: any): Type
|