@gesslar/toolkit 0.1.6 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gesslar/toolkit",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Get in, bitches, we're going toolkitting.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ export {default as FS} from "./lib/FS.js"
5
5
 
6
6
  // Utility classes
7
7
  export {default as Cache} from "./lib/Cache.js"
8
+ export {default as Collection} from "./lib/Collection.js"
8
9
  export {default as Data} from "./lib/Data.js"
9
10
  export {default as Glog} from "./lib/Glog.js"
10
11
  export {default as Sass} from "./lib/Sass.js"
@@ -0,0 +1,108 @@
1
+ import Data from "./Data.js"
2
+ import Valid from "./Valid.js"
3
+
4
+ export default class Collection {
5
+ static evalArray(collection, predicate, forward=true) {
6
+ const req = "Array"
7
+ const type = Data.typeOf(collection)
8
+
9
+ Valid.type(collection, req, `Invalid collection. Expected '${req}, got ${type}`)
10
+ Valid.type(predicate, "Function",
11
+ `Invalid predicate, expected 'Function', got ${Data.typeOf(predicate)}`)
12
+
13
+ const work = forward
14
+ ? Array.from(collection)
15
+ : Array.from(collection).toReversed()
16
+
17
+ for(let i = 0; i < work.length; i++) {
18
+ const result = predicate(work[i], i, collection) ?? null
19
+
20
+ if(result)
21
+ return result
22
+ }
23
+ }
24
+
25
+ static evalObject(collection, predicate) {
26
+ const req = "Object"
27
+ const type = Data.typeOf(collection)
28
+
29
+ Valid.type(collection, req, `Invalid collection. Expected '${req}, got ${type}`)
30
+ Valid.type(predicate, "Function",
31
+ `Invalid predicate, expected 'Function', got ${Data.typeOf(predicate)}`)
32
+
33
+ const work = Object.entries(collection)
34
+
35
+ for(let i = 0; i < work.length; i++) {
36
+ const result = predicate(work[i][1], work[i][0], collection)
37
+
38
+ if(result)
39
+ return result
40
+ }
41
+ }
42
+
43
+ static evalSet(collection, predicate) {
44
+ const req = "Set"
45
+ const type = Data.typeOf(collection)
46
+
47
+ Valid.type(collection, req, `Invalid collection. Expected '${req}, got ${type}`)
48
+ Valid.type(predicate, "Function",
49
+ `Invalid predicate, expected 'Function', got ${Data.typeOf(predicate)}`)
50
+
51
+ const work = Array.from(collection)
52
+
53
+ for(let i = 0; i < work.length; i++) {
54
+ const result = predicate(work[i], collection)
55
+
56
+ if(result)
57
+ return result
58
+ }
59
+ }
60
+
61
+ static evalMap(collection, predicate, forward=true) {
62
+ const req = "Map"
63
+ const type = Data.typeOf(collection)
64
+
65
+ Valid.type(collection, req, `Invalid collection. Expected '${req}, got ${type}`)
66
+ Valid.type(predicate, "Function",
67
+ `Invalid predicate, expected 'Function', got ${Data.typeOf(predicate)}`)
68
+
69
+ const work = forward
70
+ ? Array.from(collection)
71
+ : Array.from(collection).toReversed()
72
+
73
+ for(let i = 0; i < work.length; i++) {
74
+ const result = predicate(work[i][1], work[i][0], collection) ?? null
75
+
76
+ if(result)
77
+ return result
78
+ }
79
+ }
80
+
81
+ static zip(array1, array2) {
82
+ const minLength = Math.min(array1.length, array2.length)
83
+
84
+ return Array.from({length: minLength}, (_, i) => [array1[i], array2[i]])
85
+ }
86
+
87
+ static unzip(array) {
88
+ if(!Array.isArray(array) || array.length === 0) {
89
+ return [] // Handle empty or invalid input
90
+ }
91
+
92
+ // Determine the number of "unzipped" arrays needed
93
+ // This assumes all inner arrays have the same length, or we take the max length
94
+ const numUnzippedArrays = Math.max(...array.map(arr => arr.length))
95
+
96
+ // Initialize an array of empty arrays to hold the unzipped results
97
+ const unzipped = Array.from({length: numUnzippedArrays}, () => [])
98
+
99
+ // Iterate through the zipped array and populate the unzipped arrays
100
+ for(let i = 0; i < array.length; i++) {
101
+ for(let j = 0; j < numUnzippedArrays; j++) {
102
+ unzipped[j].push(array[i][j])
103
+ }
104
+ }
105
+
106
+ return unzipped
107
+ }
108
+ }
package/src/lib/Data.js CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import Sass from "./Sass.js"
10
10
  import TypeSpec from "./TypeSpec.js"
11
+ import Util from "./Util.js"
11
12
  import Valid from "./Valid.js"
12
13
 
13
14
  export default class Data {
@@ -19,17 +20,17 @@ export default class Data {
19
20
  */
20
21
  static primitives = Object.freeze([
21
22
  // Primitives
22
- "undefined",
23
- "null",
24
- "boolean",
25
- "number",
26
- "bigint",
27
- "string",
28
- "symbol",
23
+ "Undefined",
24
+ "Null",
25
+ "Boolean",
26
+ "Number",
27
+ "Bigint",
28
+ "String",
29
+ "Symbol",
29
30
 
30
31
  // Object Categories from typeof
31
- "object",
32
- "function",
32
+ "Object",
33
+ "Function",
33
34
  ])
34
35
 
35
36
  /**
@@ -67,7 +68,7 @@ export default class Data {
67
68
  */
68
69
  static dataTypes = Object.freeze([
69
70
  ...Data.primitives,
70
- ...Data.constructors.map(c => c.toLowerCase())
71
+ ...Data.constructors
71
72
  ])
72
73
 
73
74
  /**
@@ -76,7 +77,7 @@ export default class Data {
76
77
  *
77
78
  * @type {Array<string>}
78
79
  */
79
- static emptyableTypes = Object.freeze(["string", "array", "object"])
80
+ static emptyableTypes = Object.freeze(["String", "Array", "Object"])
80
81
 
81
82
  /**
82
83
  * Appends a string to another string if it does not already end with it.
@@ -109,8 +110,11 @@ export default class Data {
109
110
  * @returns {boolean} Whether all elements are of the specified type
110
111
  */
111
112
  static isArrayUniform(arr, type) {
113
+ const checkType = type ? Util.capitalize(type) : null
114
+
112
115
  return arr.every(
113
- (item, _index, arr) => typeof item === (type || typeof arr[0]),
116
+ (item, _index, arr) =>
117
+ Data.typeOf(item) === (checkType || Data.typeOf(arr[0])),
114
118
  )
115
119
  }
116
120
 
@@ -197,10 +201,10 @@ export default class Data {
197
201
  const result = {}
198
202
 
199
203
  for(const [key, value] of Object.entries(obj)) {
200
- if(Data.isType(value, "array")) {
204
+ if(Data.isType(value, "Array")) {
201
205
  // Clone arrays by mapping over them
202
206
  result[key] = value.map(item =>
203
- Data.isType(item, "object") || Data.isType(item, "array")
207
+ Data.isType(item, "object") || Data.isType(item, "Array")
204
208
  ? Data.cloneObject(item)
205
209
  : item
206
210
  )
@@ -227,25 +231,25 @@ export default class Data {
227
231
  workSpec = [],
228
232
  result = {}
229
233
 
230
- if(!Data.isType(source, "array", {allowEmpty: false}))
234
+ if(!Data.isType(source, "Array", {allowEmpty: false}))
231
235
  throw Sass.new("Source must be an array.")
232
236
 
233
237
  workSource.push(...source)
234
238
 
235
239
  if(
236
- !Data.isType(spec, "array", {allowEmpty: false}) &&
240
+ !Data.isType(spec, "Array", {allowEmpty: false}) &&
237
241
  !Data.isType(spec, "function")
238
242
  )
239
243
  throw Sass.new("Spec must be an array or a function.")
240
244
 
241
- if(Data.isType(spec, "function")) {
245
+ if(Data.isType(spec, "Function")) {
242
246
  const specResult = await spec(workSource)
243
247
 
244
- if(!Data.isType(specResult, "array"))
248
+ if(!Data.isType(specResult, "Array"))
245
249
  throw Sass.new("Spec resulting from function must be an array.")
246
250
 
247
251
  workSpec.push(...specResult)
248
- } else if(Data.isType(spec, "array", {allowEmpty: false})) {
252
+ } else if(Data.isType(spec, "Array", {allowEmpty: false})) {
249
253
  workSpec.push(...spec)
250
254
  }
251
255
 
@@ -256,7 +260,7 @@ export default class Data {
256
260
  workSource.map((element, index, arr) => (arr[index] = String(element)))
257
261
 
258
262
  // Check that all keys are strings
259
- if(!Data.isArrayUniform(workSource, "string"))
263
+ if(!Data.isArrayUniform(workSource, "String"))
260
264
  throw Sass.new("Indices of an Object must be of type string.")
261
265
 
262
266
  workSource.forEach((element, index) => (result[element] = workSpec[index]))
@@ -332,7 +336,13 @@ export default class Data {
332
336
  * @returns {boolean} Whether the type is valid
333
337
  */
334
338
  static isValidType(type) {
335
- return Data.dataTypes.includes(type)
339
+ // Allow built-in types
340
+ if(Data.dataTypes.includes(type)) {
341
+ return true
342
+ }
343
+
344
+ // Allow custom classes (PascalCase starting with capital letter)
345
+ return /^[A-Z][a-zA-Z0-9]*$/.test(type)
336
346
  }
337
347
 
338
348
  /**
@@ -349,16 +359,13 @@ export default class Data {
349
359
  return false
350
360
 
351
361
  const valueType = Data.typeOf(value)
352
- const normalizedType = type.toLowerCase()
353
362
 
354
363
  // Special cases that need extra validation
355
- switch(normalizedType) {
356
- case "number":
357
- return valueType === "number" && !isNaN(value) // Excludes NaN
358
- case "object":
359
- return valueType === "object" && value !== null && !Array.isArray(value) // Excludes arrays and null
364
+ switch(valueType) {
365
+ case "Number":
366
+ return valueType === "Number" && !isNaN(value) // Excludes NaN
360
367
  default:
361
- return valueType === normalizedType
368
+ return valueType === type
362
369
  }
363
370
  }
364
371
 
@@ -370,12 +377,13 @@ export default class Data {
370
377
  */
371
378
  static typeOf(value) {
372
379
  if(value === null)
373
- return "null"
380
+ return "Null"
374
381
 
375
- if(Array.isArray(value))
376
- return "array"
382
+ const type = typeof value
377
383
 
378
- return typeof value
384
+ return type === "object"
385
+ ? value.constructor.name
386
+ : type.charAt(0).toUpperCase() + type.slice(1)
379
387
  }
380
388
 
381
389
  /**
@@ -412,12 +420,12 @@ export default class Data {
412
420
  return false
413
421
 
414
422
  switch(type) {
415
- case "array":
423
+ case "Array":
416
424
  return value.length === 0
417
- case "object":
425
+ case "Object":
418
426
  // null was already handled above, so this should only be real objects
419
427
  return Object.keys(value).length === 0
420
- case "string":
428
+ case "String":
421
429
  return value.trim().length === 0
422
430
  default:
423
431
  return false
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Sass from "./Sass.js"
8
8
  import Data from "./Data.js"
9
+ import Util from "./Util.js"
9
10
 
10
11
  /**
11
12
  * Type specification class for parsing and validating complex type definitions.
@@ -149,7 +150,7 @@ export default class TypeSpec {
149
150
  // Now, let's do some checking with the types, respecting the array flag
150
151
  // with the value
151
152
  const valueType = Data.typeOf(value)
152
- const isArray = valueType === "array"
153
+ const isArray = valueType === "Array"
153
154
 
154
155
  // We need to ensure that we match the type and the consistency of the types
155
156
  // in an array, if it is an array and an array is allowed.
@@ -166,8 +167,8 @@ export default class TypeSpec {
166
167
 
167
168
  // Handle array values
168
169
  if(isArray) {
169
- // Special case for generic "array" type
170
- if(allowedType === "array" && !allowedArray)
170
+ // Special case for generic "Array" type
171
+ if(allowedType === "Array" && !allowedArray)
171
172
  return allowEmpty || !empty
172
173
 
173
174
  // Must be an array type specification
@@ -203,16 +204,18 @@ export default class TypeSpec {
203
204
  const parts = string.split(delimiter)
204
205
 
205
206
  this.#specs = parts.map(part => {
206
- const typeMatches = /(\w+)(\[\])?/.exec(part)
207
+ const typeMatches = /^(\w+)(\[\])?$/.exec(part)
207
208
 
208
209
  if(!typeMatches || typeMatches.length !== 3)
209
210
  throw Sass.new(`Invalid type: ${part}`)
210
211
 
211
- if(!Data.isValidType(typeMatches[1]))
212
+ const typeName = Util.capitalize(typeMatches[1])
213
+
214
+ if(!Data.isValidType(typeName))
212
215
  throw Sass.new(`Invalid type: ${typeMatches[1]}`)
213
216
 
214
217
  return {
215
- typeName: typeMatches[1],
218
+ typeName,
216
219
  array: typeMatches[2] === "[]",
217
220
  }
218
221
  })
@@ -0,0 +1,138 @@
1
+ // Implementation: ../lib/Collection.js
2
+ // Type definitions for Collection utilities
3
+
4
+ /**
5
+ * Collection utility functions for evaluating and manipulating arrays, objects, sets, and maps.
6
+ * Provides functional programming patterns for collection processing with consistent error handling.
7
+ */
8
+ export default class Collection {
9
+ /**
10
+ * Evaluates an array with a predicate function, returning the first truthy result.
11
+ *
12
+ * @param collection - The array to evaluate
13
+ * @param predicate - Function called for each element: (element, index, array) => result
14
+ * @param forward - Whether to iterate forward (true) or backward (false). Default: true
15
+ * @returns The first truthy result from the predicate, or undefined if none found
16
+ *
17
+ * @throws {Sass} If collection is not an Array or predicate is not a Function
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { Collection } from '@gesslar/toolkit'
22
+ *
23
+ * const numbers = [1, 2, 3, 4, 5]
24
+ * const result = Collection.evalArray(numbers, (n, i) => n > 3 ? n * 2 : null)
25
+ * console.log(result) // 8 (first element > 3, doubled)
26
+ * ```
27
+ */
28
+ static evalArray<T, R>(
29
+ collection: T[],
30
+ predicate: (element: T, index: number, array: T[]) => R | null | undefined,
31
+ forward?: boolean
32
+ ): R | undefined
33
+
34
+ /**
35
+ * Evaluates an object with a predicate function, returning the first truthy result.
36
+ *
37
+ * @param collection - The object to evaluate
38
+ * @param predicate - Function called for each property: (value, key, object) => result
39
+ * @returns The first truthy result from the predicate, or undefined if none found
40
+ *
41
+ * @throws {Sass} If collection is not an Object or predicate is not a Function
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { Collection } from '@gesslar/toolkit'
46
+ *
47
+ * const obj = {a: 1, b: 2, c: 3}
48
+ * const result = Collection.evalObject(obj, (value, key) => value > 2 ? `${key}:${value}` : null)
49
+ * console.log(result) // "c:3"
50
+ * ```
51
+ */
52
+ static evalObject<T, R>(
53
+ collection: Record<string, T>,
54
+ predicate: (value: T, key: string, object: Record<string, T>) => R | null | undefined
55
+ ): R | undefined
56
+
57
+ /**
58
+ * Evaluates a Set with a predicate function, returning the first truthy result.
59
+ *
60
+ * @param collection - The Set to evaluate
61
+ * @param predicate - Function called for each element: (element, set) => result
62
+ * @returns The first truthy result from the predicate, or undefined if none found
63
+ *
64
+ * @throws {Sass} If collection is not a Set or predicate is not a Function
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * import { Collection } from '@gesslar/toolkit'
69
+ *
70
+ * const set = new Set([1, 2, 3, 4, 5])
71
+ * const result = Collection.evalSet(set, (n, s) => n > 3 ? n * 2 : null)
72
+ * console.log(result) // 8
73
+ * ```
74
+ */
75
+ static evalSet<T, R>(
76
+ collection: Set<T>,
77
+ predicate: (element: T, set: Set<T>) => R | null | undefined
78
+ ): R | undefined
79
+
80
+ /**
81
+ * Evaluates a Map with a predicate function, returning the first truthy result.
82
+ *
83
+ * @param collection - The Map to evaluate
84
+ * @param predicate - Function called for each entry: (value, key, map) => result
85
+ * @param forward - Whether to iterate forward (true) or backward (false). Default: true
86
+ * @returns The first truthy result from the predicate, or undefined if none found
87
+ *
88
+ * @throws {Sass} If collection is not a Map or predicate is not a Function
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * import { Collection } from '@gesslar/toolkit'
93
+ *
94
+ * const map = new Map([['a', 1], ['b', 2], ['c', 3]])
95
+ * const result = Collection.evalMap(map, (value, key) => value > 2 ? `${key}:${value}` : null)
96
+ * console.log(result) // "c:3"
97
+ * ```
98
+ */
99
+ static evalMap<K, V, R>(
100
+ collection: Map<K, V>,
101
+ predicate: (value: V, key: K, map: Map<K, V>) => R | null | undefined,
102
+ forward?: boolean
103
+ ): R | undefined
104
+
105
+ /**
106
+ * Zips two arrays together into an array of pairs.
107
+ *
108
+ * @param array1 - The first array
109
+ * @param array2 - The second array
110
+ * @returns Array of [element1, element2] pairs, length of shorter input array
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * import { Collection } from '@gesslar/toolkit'
115
+ *
116
+ * const result = Collection.zip([1, 2, 3], ['a', 'b', 'c'])
117
+ * console.log(result) // [[1, 'a'], [2, 'b'], [3, 'c']]
118
+ * ```
119
+ */
120
+ static zip<T, U>(array1: T[], array2: U[]): [T, U][]
121
+
122
+ /**
123
+ * Unzips an array of arrays into separate arrays.
124
+ *
125
+ * @param array - Array of arrays to unzip
126
+ * @returns Array of separate arrays, one for each position
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * import { Collection } from '@gesslar/toolkit'
131
+ *
132
+ * const zipped = [[1, 'a'], [2, 'b'], [3, 'c']]
133
+ * const result = Collection.unzip(zipped)
134
+ * console.log(result) // [[1, 2, 3], ['a', 'b', 'c']]
135
+ * ```
136
+ */
137
+ static unzip<T>(array: T[][]): T[][]
138
+ }
@@ -6,6 +6,7 @@ export { default as FS } from './FS.js'
6
6
 
7
7
  // Utility classes
8
8
  export { default as Cache } from './Cache.js'
9
+ export { default as Collection } from './Collection.js'
9
10
  export { default as Data } from './Data.js'
10
11
  export { default as Glog } from './Glog.js'
11
12
  export { default as Sass } from './Sass.js'