@zairakai/js-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/arrays.cjs +210 -0
  4. package/dist/arrays.d.cts +119 -0
  5. package/dist/arrays.d.ts +119 -0
  6. package/dist/arrays.js +32 -0
  7. package/dist/chunk-27YHP2CK.js +407 -0
  8. package/dist/chunk-3WNRYKPG.js +37 -0
  9. package/dist/chunk-42CHLXT7.js +214 -0
  10. package/dist/chunk-6F4PWJZI.js +0 -0
  11. package/dist/chunk-7SXRFZBB.js +173 -0
  12. package/dist/chunk-F6RSTW65.js +156 -0
  13. package/dist/chunk-G7ZJ23DW.js +253 -0
  14. package/dist/chunk-IPP7PA6H.js +136 -0
  15. package/dist/chunk-LDSWHSRX.js +96 -0
  16. package/dist/chunk-TY75OOIQ.js +700 -0
  17. package/dist/chunk-W6JEMFAF.js +54 -0
  18. package/dist/chunk-XEJLBAXE.js +164 -0
  19. package/dist/chunk-Z7G3SIQH.js +270 -0
  20. package/dist/chunk-ZJPKS2MQ.js +101 -0
  21. package/dist/collections.cjs +797 -0
  22. package/dist/collections.d.cts +353 -0
  23. package/dist/collections.d.ts +353 -0
  24. package/dist/collections.js +17 -0
  25. package/dist/datetime.cjs +80 -0
  26. package/dist/datetime.d.cts +75 -0
  27. package/dist/datetime.d.ts +75 -0
  28. package/dist/datetime.js +24 -0
  29. package/dist/equals.cjs +121 -0
  30. package/dist/equals.d.cts +24 -0
  31. package/dist/equals.d.ts +24 -0
  32. package/dist/equals.js +8 -0
  33. package/dist/formatters.cjs +201 -0
  34. package/dist/formatters.d.cts +180 -0
  35. package/dist/formatters.d.ts +180 -0
  36. package/dist/formatters.js +48 -0
  37. package/dist/index.cjs +2906 -0
  38. package/dist/index.d.cts +120 -0
  39. package/dist/index.d.ts +120 -0
  40. package/dist/index.js +348 -0
  41. package/dist/number.cjs +279 -0
  42. package/dist/number.d.cts +177 -0
  43. package/dist/number.d.ts +177 -0
  44. package/dist/number.js +10 -0
  45. package/dist/obj.cjs +427 -0
  46. package/dist/obj.d.cts +177 -0
  47. package/dist/obj.d.ts +177 -0
  48. package/dist/obj.js +12 -0
  49. package/dist/php-arrays.cjs +954 -0
  50. package/dist/php-arrays.d.cts +256 -0
  51. package/dist/php-arrays.d.ts +256 -0
  52. package/dist/php-arrays.js +70 -0
  53. package/dist/runtime.cjs +134 -0
  54. package/dist/runtime.d.cts +90 -0
  55. package/dist/runtime.d.ts +90 -0
  56. package/dist/runtime.js +24 -0
  57. package/dist/schemas.cjs +86 -0
  58. package/dist/schemas.d.cts +108 -0
  59. package/dist/schemas.d.ts +108 -0
  60. package/dist/schemas.js +22 -0
  61. package/dist/str.cjs +499 -0
  62. package/dist/str.d.cts +282 -0
  63. package/dist/str.d.ts +282 -0
  64. package/dist/str.js +11 -0
  65. package/dist/types.cjs +18 -0
  66. package/dist/types.d.cts +13 -0
  67. package/dist/types.d.ts +13 -0
  68. package/dist/types.js +1 -0
  69. package/dist/validator.cjs +251 -0
  70. package/dist/validator.d.cts +99 -0
  71. package/dist/validator.d.ts +99 -0
  72. package/dist/validator.js +11 -0
  73. package/dist/validators.cjs +217 -0
  74. package/dist/validators.d.cts +216 -0
  75. package/dist/validators.d.ts +216 -0
  76. package/dist/validators.js +64 -0
  77. package/package.json +180 -0
  78. package/src/arrays.ts +316 -0
  79. package/src/collections.ts +866 -0
  80. package/src/datetime.ts +103 -0
  81. package/src/equals.ts +134 -0
  82. package/src/formatters.ts +342 -0
  83. package/src/index.ts +36 -0
  84. package/src/number.ts +281 -0
  85. package/src/obj.ts +303 -0
  86. package/src/php-arrays.ts +445 -0
  87. package/src/pipe.ts +29 -0
  88. package/src/runtime.ts +194 -0
  89. package/src/schemas.ts +136 -0
  90. package/src/str.ts +438 -0
  91. package/src/types.ts +13 -0
  92. package/src/validator.ts +157 -0
  93. package/src/validators.ts +359 -0
@@ -0,0 +1,445 @@
1
+ /**
2
+ * PHP-like array functions for JavaScript
3
+ * Provides exact API compatibility between PHP arrays and JavaScript arrays
4
+ */
5
+
6
+ import { EnhancedArray } from './collections.js'
7
+
8
+ // PHP array function equivalents
9
+ /**
10
+ * Split an array into chunks.
11
+ *
12
+ * @param {T[]} array The array to chunk
13
+ * @param {number} size The size of each chunk
14
+ * @returns {T[][]} An array of chunks
15
+ */
16
+ export const array_chunk = <T>(array: T[], size: number): T[][] => {
17
+ if (0 >= size) {
18
+ return []
19
+ }
20
+ const chunks: T[][] = []
21
+ for (let i = 0; i < array.length; i += size) {
22
+ chunks.push(array.slice(i, i + size))
23
+ }
24
+ return chunks
25
+ }
26
+
27
+ /**
28
+ * Filter elements of an array using a callback function.
29
+ *
30
+ * @param {T[]} array The array to filter
31
+ * @param {Function} [callback] The callback function to use
32
+ * @returns {T[]} The filtered array
33
+ */
34
+ export const array_filter = <T>(array: T[], callback?: (value: T, index: number) => boolean): T[] => {
35
+ if (!callback) {
36
+ return array.filter((item) => Boolean(item))
37
+ }
38
+ return array.filter(callback)
39
+ }
40
+
41
+ /**
42
+ * Apply a callback to the elements of the given arrays.
43
+ *
44
+ * @param {Function} callback The callback function to apply
45
+ * @param {T[]} array The array to map over
46
+ * @returns {U[]} The mapped array
47
+ */
48
+ export const array_map = <T, U>(callback: (value: T, index: number) => U, array: T[]): U[] => {
49
+ return array.map(callback)
50
+ }
51
+
52
+ /**
53
+ * Iteratively reduce the array to a single value using a callback function.
54
+ *
55
+ * @param {T[]} array The source array
56
+ * @param {Function} callback The callback function
57
+ * @param {U} initial The initial value
58
+ * @returns {U} The reduced value
59
+ */
60
+ export const array_reduce = <T, U>(array: T[], callback: (carry: U, item: T, index: number) => U, initial: U): U => {
61
+ return array.reduce((carry, item, index) => callback(carry, item, index), initial)
62
+ }
63
+
64
+ /**
65
+ * Merge one or more arrays.
66
+ *
67
+ * @param {...T[][]} arrays The arrays to merge
68
+ * @returns {T[]} The merged array
69
+ */
70
+ export const array_merge = <T>(...arrays: T[][]): T[] => {
71
+ return arrays.flat()
72
+ }
73
+
74
+ /**
75
+ * Remove duplicate values from an array.
76
+ *
77
+ * @param {T[]} array The source array
78
+ * @returns {T[]} The array with unique values
79
+ */
80
+ export const array_unique = <T>(array: T[]): T[] => {
81
+ return [...new Set(array)]
82
+ }
83
+
84
+ /**
85
+ * Return an array with elements in reverse order.
86
+ *
87
+ * @param {T[]} array The source array
88
+ * @param {boolean} [_preserveKeys=false] Whether to preserve keys (ignored for JS arrays)
89
+ * @returns {T[]} The reversed array
90
+ */
91
+ export const array_reverse = <T>(array: T[], _preserveKeys: boolean = false): T[] => {
92
+ return [...array].reverse()
93
+ }
94
+
95
+ /**
96
+ * Extract a slice of the array.
97
+ *
98
+ * @param {T[]} array The source array
99
+ * @param {number} offset The starting offset
100
+ * @param {number} [length] The length of the slice
101
+ * @returns {T[]} The sliced portion of the array
102
+ */
103
+ export const array_slice = <T>(array: T[], offset: number, length?: number): T[] => {
104
+ const start = 0 > offset ? Math.max(0, array.length + offset) : offset
105
+ const end = length !== undefined ? start + length : undefined
106
+ return array.slice(start, end)
107
+ }
108
+
109
+ /**
110
+ * Remove a portion of the array and replace it with something else.
111
+ *
112
+ * @param {T[]} array The source array
113
+ * @param {number} offset The starting offset
114
+ * @param {number} [length] The number of elements to remove
115
+ * @param {...T[]} replacement The elements to replace with
116
+ * @returns {T[]} The modified array (copy)
117
+ */
118
+ export const array_splice = <T>(array: T[], offset: number, length?: number, ...replacement: T[]): T[] => {
119
+ const result = [...array]
120
+ const actualLength = length ?? result.length - offset
121
+ result.splice(offset, actualLength, ...replacement)
122
+ return result
123
+ }
124
+
125
+ /**
126
+ * Return all the keys or a subset of the keys of an array.
127
+ *
128
+ * @param {T[]} array The source array
129
+ * @returns {number[]} The array keys (indices)
130
+ */
131
+ export const array_keys = <T>(array: T[]): number[] => {
132
+ return array.map((_, index) => index)
133
+ }
134
+
135
+ /**
136
+ * Search an array for a given value and return the corresponding key if successful.
137
+ *
138
+ * @param {T} needle The value to search for
139
+ * @param {T[]} haystack The array to search in
140
+ * @returns {number | false} The key if found, false otherwise
141
+ */
142
+ export const array_search = <T>(needle: T, haystack: T[]): number | false => {
143
+ const index = haystack.indexOf(needle)
144
+ return -1 !== index ? index : false
145
+ }
146
+
147
+ /**
148
+ * Check if the given key or index exists in the array.
149
+ *
150
+ * @param {number} key The key to check
151
+ * @param {T[]} array The source array
152
+ * @returns {boolean} True if the key exists
153
+ */
154
+ export const array_key_exists = <T>(key: number, array: T[]): boolean => {
155
+ return 0 <= key && key < array.length
156
+ }
157
+
158
+ /**
159
+ * Pop the element off the end of array.
160
+ *
161
+ * @param {T[]} array The source array
162
+ * @returns {T | undefined} The popped element
163
+ */
164
+ export const array_pop = <T>(array: T[]): T | undefined => {
165
+ const result = [...array]
166
+ return result.pop()
167
+ }
168
+
169
+ /**
170
+ * Push one or more elements onto the end of array.
171
+ *
172
+ * @param {T[]} array The source array
173
+ * @param {...T[]} values The values to push
174
+ * @returns {T[]} The modified array (copy)
175
+ */
176
+ export const array_push = <T>(array: T[], ...values: T[]): T[] => {
177
+ return [...array, ...values]
178
+ }
179
+
180
+ /**
181
+ * Shift an element off the beginning of array.
182
+ *
183
+ * @param {T[]} array The source array
184
+ * @returns {T | undefined} The shifted element
185
+ */
186
+ export const array_shift = <T>(array: T[]): T | undefined => {
187
+ const result = [...array]
188
+ return result.shift()
189
+ }
190
+
191
+ /**
192
+ * Prepend one or more elements to the beginning of an array.
193
+ *
194
+ * @param {T[]} array The source array
195
+ * @param {...T[]} values The values to prepend
196
+ * @returns {T[]} The modified array (copy)
197
+ */
198
+ export const array_unshift = <T>(array: T[], ...values: T[]): T[] => {
199
+ return [...values, ...array]
200
+ }
201
+
202
+ /**
203
+ * Calculate the sum of values in an array.
204
+ *
205
+ * @param {number[]} array The source array
206
+ * @returns {number} The sum of values
207
+ */
208
+ export const array_sum = (array: number[]): number => {
209
+ return array.reduce((sum, num) => sum + num, 0)
210
+ }
211
+
212
+ /**
213
+ * Calculate the product of values in an array.
214
+ *
215
+ * @param {number[]} array The source array
216
+ * @returns {number} The product of values
217
+ */
218
+ export const array_product = (array: number[]): number => {
219
+ return array.reduce((product, num) => product * num, 1)
220
+ }
221
+
222
+ /**
223
+ * Pick one or more random entries out of an array.
224
+ *
225
+ * @param {T[]} array The source array
226
+ * @param {number} [num=1] The number of entries to pick
227
+ * @returns {T | T[] | undefined} The random entry or entries
228
+ */
229
+ export const array_rand = <T>(array: T[], num: number = 1): T | T[] | undefined => {
230
+ if (0 === array.length) {
231
+ return 1 === num ? undefined : []
232
+ }
233
+
234
+ const shuffled = [...array].sort(() => Math.random() - 0.5)
235
+
236
+ if (1 === num) {
237
+ return shuffled[0]
238
+ }
239
+
240
+ return shuffled.slice(0, Math.min(num, array.length))
241
+ }
242
+
243
+ /**
244
+ * Exchange all keys with their associated values in an array.
245
+ *
246
+ * @param {T[]} array The source array
247
+ * @returns {Record<string, number>} The flipped record
248
+ */
249
+ export const array_flip = <T extends string | number>(array: T[]): Record<string, number> => {
250
+ const result: Record<string, number> = {}
251
+ array.forEach((value, index) => {
252
+ result[String(value)] = index
253
+ })
254
+ return result
255
+ }
256
+
257
+ /**
258
+ * Count all the values of an array.
259
+ *
260
+ * @param {T[]} array The source array
261
+ * @returns {Record<string, number>} A record of values and their counts
262
+ */
263
+ export const array_count_values = <T extends string | number>(array: T[]): Record<string, number> => {
264
+ const result: Record<string, number> = {}
265
+ array.forEach((value) => {
266
+ const key = String(value)
267
+ result[key] = (result[key] || 0) + 1
268
+ })
269
+ return result
270
+ }
271
+
272
+ // Advanced PHP array functions
273
+ /**
274
+ * Compute the intersection of arrays.
275
+ *
276
+ * @param {...T[][]} arrays The arrays to intersect
277
+ * @returns {T[]} The intersection of the arrays
278
+ */
279
+ export const array_intersect = <T>(...arrays: T[][]): T[] => {
280
+ if (0 === arrays.length) {
281
+ return []
282
+ }
283
+
284
+ const first = arrays[0]
285
+ return first.filter((item) => arrays.slice(1).every((arr) => arr.includes(item)))
286
+ }
287
+
288
+ /**
289
+ * Compute the difference of arrays.
290
+ *
291
+ * @param {T[]} array1 The array to compare from
292
+ * @param {...T[][]} arrays The arrays to compare against
293
+ * @returns {T[]} The difference of the arrays
294
+ */
295
+ export const array_diff = <T>(array1: T[], ...arrays: T[][]): T[] => {
296
+ const otherItems = new Set(arrays.flat())
297
+ return array1.filter((item) => !otherItems.has(item))
298
+ }
299
+
300
+ /**
301
+ * Return the values from a single column in the input array.
302
+ *
303
+ * @param {T[]} array The source array of objects
304
+ * @param {K} column The column to retrieve
305
+ * @returns {T[K][]} The array of column values
306
+ */
307
+ export const array_column = <T, K extends keyof T>(array: T[], column: K): T[K][] => {
308
+ return array
309
+ .map((item) => (item && 'object' === typeof item ? item[column] : undefined))
310
+ .filter((value) => value !== undefined) as T[K][]
311
+ }
312
+
313
+ // PHP sorting functions
314
+ /**
315
+ * Sort an array.
316
+ *
317
+ * @param {T[]} array The source array
318
+ * @returns {T[]} The sorted array (copy)
319
+ */
320
+ export const sort = <T>(array: T[]): T[] => {
321
+ return [...array].sort()
322
+ }
323
+
324
+ /**
325
+ * Sort an array in reverse order.
326
+ *
327
+ * @param {T[]} array The source array
328
+ * @returns {T[]} The sorted array (copy)
329
+ */
330
+ export const rsort = <T>(array: T[]): T[] => {
331
+ return [...array].sort().reverse()
332
+ }
333
+
334
+ /**
335
+ * Sort an array with a user-defined comparison function.
336
+ *
337
+ * @param {T[]} array The source array
338
+ * @param {Function} compareFunction The comparison function
339
+ * @returns {T[]} The sorted array (copy)
340
+ */
341
+ export const usort = <T>(array: T[], compareFunction: (a: T, b: T) => number): T[] => {
342
+ return [...array].sort(compareFunction)
343
+ }
344
+
345
+ /**
346
+ * Sort an array with a user-defined comparison function and maintain index association.
347
+ *
348
+ * @param {T[]} array The source array
349
+ * @param {Function} compareFunction The comparison function
350
+ * @returns {T[]} The sorted array (copy)
351
+ */
352
+ export const uasort = <T>(array: T[], compareFunction: (a: T, b: T) => number): T[] => {
353
+ return [...array].sort(compareFunction)
354
+ }
355
+
356
+ /**
357
+ * Sort an array by keys using a user-defined comparison function.
358
+ *
359
+ * @param {T[]} array The source array
360
+ * @param {Function} compareFunction The comparison function
361
+ * @returns {T[]} The sorted array (copy)
362
+ */
363
+ export const uksort = <T>(array: T[], compareFunction: (a: number, b: number) => number): T[] => {
364
+ const indices = Array.from({ length: array.length }, (_, i) => i)
365
+ const sortedIndices = indices.sort(compareFunction)
366
+ return sortedIndices.map((i) => array[i])
367
+ }
368
+
369
+ /**
370
+ * Shuffle an array.
371
+ *
372
+ * @param {T[]} array The source array
373
+ * @returns {T[]} The shuffled array (copy)
374
+ */
375
+ export const shuffle = <T>(array: T[]): T[] => {
376
+ const result = [...array]
377
+ for (let i = result.length - 1; 0 < i; i--) {
378
+ const j = Math.floor(Math.random() * (i + 1))
379
+ ;[result[i], result[j]] = [result[j], result[i]]
380
+ }
381
+ return result
382
+ }
383
+
384
+ // Range function like PHP
385
+ /**
386
+ * Create an array containing a range of elements.
387
+ *
388
+ * @param {number} start First value of the sequence
389
+ * @param {number} end The sequence is stopped when end is reached
390
+ * @param {number} [step=1] The increment used in the range
391
+ * @returns {number[]} The array of elements
392
+ */
393
+ export const range = (start: number, end: number, step: number = 1): number[] => {
394
+ const result: number[] = []
395
+
396
+ if (0 < step) {
397
+ for (let i = start; i <= end; i += step) {
398
+ result.push(i)
399
+ }
400
+ } else if (0 > step) {
401
+ for (let i = start; i >= end; i += step) {
402
+ result.push(i)
403
+ }
404
+ }
405
+
406
+ return result
407
+ }
408
+
409
+ // Create PHP-like array from JavaScript array
410
+ /**
411
+ * Create an EnhancedArray with PHP-like methods.
412
+ *
413
+ * @param {T[]} items The source items
414
+ * @returns {EnhancedArray<T>} The enhanced array
415
+ */
416
+ export const php_array = <T>(items: T[]): EnhancedArray<T> => {
417
+ const enhanced = new EnhancedArray(...items)
418
+
419
+ // Add PHP-like methods
420
+ Object.assign(enhanced, {
421
+ // PHP array functions as methods
422
+ chunk: (size: number) => array_chunk(enhanced, size),
423
+ merge: (...arrays: T[][]) => new EnhancedArray(...array_merge(enhanced, ...arrays)),
424
+ unique: () => new EnhancedArray(...array_unique(enhanced)),
425
+ reverse: (preserveKeys = false) => new EnhancedArray(...array_reverse(enhanced, preserveKeys)),
426
+ search: (needle: T) => array_search(needle, enhanced),
427
+ sum: () => array_sum(enhanced as unknown as number[]),
428
+ product: () => array_product(enhanced as unknown as number[]),
429
+ rand: (num = 1) => array_rand(enhanced, num),
430
+ flip: () => array_flip(enhanced as unknown as (string | number)[]),
431
+ countValues: () => array_count_values(enhanced as unknown as (string | number)[]),
432
+ intersect: (...arrays: T[][]) => new EnhancedArray(...array_intersect(enhanced, ...arrays)),
433
+ diff: (...arrays: T[][]) => new EnhancedArray(...array_diff(enhanced, ...arrays)),
434
+ column: <K extends keyof T>(column: K) =>
435
+ (array_column as (arr: unknown[], col: unknown) => T[K][])(enhanced, column),
436
+
437
+ // PHP sorting as methods
438
+ sort: () => new EnhancedArray(...sort(enhanced)),
439
+ rsort: () => new EnhancedArray(...rsort(enhanced)),
440
+ shuffle: () => new EnhancedArray(...shuffle(enhanced)),
441
+ usort: (compareFunction: (a: T, b: T) => number) => new EnhancedArray(...usort(enhanced, compareFunction)),
442
+ })
443
+
444
+ return enhanced
445
+ }
package/src/pipe.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Functional pipe helper inspired by Laravel pipelines.
3
+ *
4
+ * @param {T} value The initial value to pipe
5
+ * @returns {Object} An object with through and value methods
6
+ */
7
+ export function pipe<T>(value: T) {
8
+ return {
9
+ /**
10
+ * Pass the value through a callback.
11
+ *
12
+ * @param {Function} fn The callback function
13
+ * @returns {ReturnType<typeof pipe>} A new pipe instance with the result
14
+ */
15
+ through<U>(fn: (input: T) => U) {
16
+ const result = fn(value)
17
+ return pipe(result)
18
+ },
19
+
20
+ /**
21
+ * Get the current value of the pipe.
22
+ *
23
+ * @returns {T} The current value
24
+ */
25
+ value() {
26
+ return value
27
+ },
28
+ }
29
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,194 @@
1
+ import { GenericRecord } from './types.js'
2
+
3
+ /**
4
+ * Runtime utility functions inspired by Laravel
5
+ */
6
+
7
+ /**
8
+ * Check if a value is a plain object (Record)
9
+ *
10
+ * @param {unknown} value The value to check
11
+ * @returns {value is GenericRecord} True if the value is a plain object
12
+ */
13
+ export function isRecord(value: unknown): value is GenericRecord {
14
+ return null !== value && 'object' === typeof value && !Array.isArray(value)
15
+ }
16
+
17
+ /**
18
+ * Execute a callback and return the given value
19
+ * Useful for side effects in a chain
20
+ *
21
+ * @param {T} value The value to pass to the callback
22
+ * @param {(value: T) => void} callback The callback to execute
23
+ * @returns {T} The original value
24
+ */
25
+ export const tap = <T>(value: T, callback: (value: T) => void): T => {
26
+ callback(value)
27
+ return value
28
+ }
29
+
30
+ /**
31
+ * Conditionally execute a callback
32
+ *
33
+ * @param {unknown} condition The condition to check (value or zero-arg function returning boolean)
34
+ * @param {() => R} onTrue The callback to execute if the condition is truthy
35
+ * @param {() => R} [onFalse] The callback to execute if the condition is falsy
36
+ * @returns {R | undefined} The result of the executed callback or undefined
37
+ */
38
+ export const when = <R>(condition: unknown, onTrue: () => R, onFalse?: () => R): R | undefined => {
39
+ const shouldExecute = 'function' === typeof condition ? (condition as () => boolean)() : Boolean(condition)
40
+
41
+ if (shouldExecute) {
42
+ return onTrue()
43
+ } else if (onFalse) {
44
+ return onFalse()
45
+ }
46
+
47
+ return undefined
48
+ }
49
+
50
+ /**
51
+ * Return the first argument if it's truthy, otherwise return the second
52
+ *
53
+ * @param {T | (() => T)} val The value or a function that returns the value
54
+ * @returns {T} The resolved value
55
+ */
56
+ export const value = <T>(val: T | (() => T)): T => {
57
+ return 'function' === typeof val ? (val as () => T)() : val
58
+ }
59
+
60
+ /**
61
+ * Get an item from an array or object using dot notation
62
+ *
63
+ * @param {unknown} target The object or array to search
64
+ * @param {string | string[]} key The dot-notated key or an array of keys
65
+ * @param {unknown} [defaultValue=null] The default value to return if the key is not found
66
+ * @returns {unknown} The found value or the default value
67
+ */
68
+ export const data_get = (target: unknown, key: string | string[], defaultValue: unknown = null): unknown => {
69
+ if (!isRecord(target) && !Array.isArray(target)) {
70
+ return defaultValue
71
+ }
72
+
73
+ const parts = Array.isArray(key) ? key : key.split('.')
74
+ let result: unknown = target
75
+
76
+ for (const part of parts) {
77
+ if (!isRecord(result) && !Array.isArray(result)) {
78
+ return defaultValue
79
+ }
80
+
81
+ if (part in (result as GenericRecord)) {
82
+ result = (result as GenericRecord)[part]
83
+ } else {
84
+ return defaultValue
85
+ }
86
+ }
87
+
88
+ return result
89
+ }
90
+
91
+ /**
92
+ * Set an item in an array or object using dot notation
93
+ *
94
+ * @param {T} target The object or array to modify
95
+ * @param {string | string[]} key The dot-notated key or an array of keys
96
+ * @param {unknown} value The value to set
97
+ * @returns {T} The modified target
98
+ */
99
+ export const data_set = <T>(target: T, key: string | string[], value: unknown): T => {
100
+ if (!isRecord(target) && !Array.isArray(target)) {
101
+ return target
102
+ }
103
+
104
+ const parts = Array.isArray(key) ? key : key.split('.')
105
+ let current: unknown = target
106
+
107
+ for (let i = 0; i < parts.length - 1; i++) {
108
+ const part = parts[i]
109
+
110
+ if (!isRecord(current)) {
111
+ break
112
+ }
113
+
114
+ const currentRecord = current as GenericRecord
115
+
116
+ if (!(part in currentRecord) || !isRecord(currentRecord[part])) {
117
+ currentRecord[part] = {} as GenericRecord
118
+ }
119
+
120
+ current = currentRecord[part]
121
+ }
122
+
123
+ if (isRecord(current)) {
124
+ ;(current as GenericRecord)[parts[parts.length - 1]] = value
125
+ }
126
+
127
+ return target
128
+ }
129
+
130
+ /**
131
+ * Returns the object if it exists, otherwise returns null (similar to optional() helper)
132
+ *
133
+ * @param {T} value The value to return if it exists
134
+ * @returns {T | null} The value or null
135
+ */
136
+ export const optional = <T>(value: T): T | null => {
137
+ return value ?? null
138
+ }
139
+
140
+ /**
141
+ * Execute a callback with retry logic
142
+ *
143
+ * @param {number} times The number of times to retry
144
+ * @param {(attempt: number) => Promise<T> | T} callback The callback to execute
145
+ * @param {number} [sleepMilliseconds=0] The amount of time to wait between retries
146
+ * @returns {Promise<T>} The result of the callback
147
+ * @throws {Error} If all attempts fail
148
+ */
149
+ export const retry = async <T>(
150
+ times: number,
151
+ callback: (attempt: number) => Promise<T> | T,
152
+ sleepMilliseconds = 0
153
+ ): Promise<T> => {
154
+ let lastError: Error | undefined
155
+
156
+ for (let attempt = 1; attempt <= times; attempt++) {
157
+ try {
158
+ return await callback(attempt)
159
+ } catch (error) {
160
+ lastError = error instanceof Error ? error : new Error(String(error))
161
+ if (attempt < times && 0 < sleepMilliseconds) {
162
+ await new Promise((resolve) => setTimeout(resolve, sleepMilliseconds))
163
+ }
164
+ }
165
+ }
166
+
167
+ throw lastError ?? new Error(`Retry failed after ${times} attempts`)
168
+ }
169
+
170
+ /**
171
+ * Throw an error if a condition is met
172
+ *
173
+ * @param {unknown} condition The condition to check
174
+ * @param {Error | string} exception The error to throw or the error message
175
+ * @throws {Error} If the condition is met
176
+ */
177
+ export const throw_if = (condition: unknown, exception: Error | string): void => {
178
+ if ('function' === typeof condition ? (condition as () => boolean)() : Boolean(condition)) {
179
+ throw 'string' === typeof exception ? new Error(exception) : exception
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Throw an error unless a condition is met
185
+ *
186
+ * @param {unknown} condition The condition to check
187
+ * @param {Error | string} exception The error to throw or the error message
188
+ * @throws {Error} If the condition is not met
189
+ */
190
+ export const throw_unless = (condition: unknown, exception: Error | string): void => {
191
+ if (!('function' === typeof condition ? (condition as () => boolean)() : Boolean(condition))) {
192
+ throw 'string' === typeof exception ? new Error(exception) : exception
193
+ }
194
+ }