@mantiq/helpers 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 +19 -0
- package/package.json +52 -0
- package/src/Arr.ts +264 -0
- package/src/Collection.ts +804 -0
- package/src/Duration.ts +172 -0
- package/src/Http.ts +573 -0
- package/src/HttpFake.ts +361 -0
- package/src/Num.ts +187 -0
- package/src/Result.ts +196 -0
- package/src/Str.ts +457 -0
- package/src/async.ts +209 -0
- package/src/functions.ts +153 -0
- package/src/index.ts +65 -0
- package/src/is.ts +195 -0
- package/src/match.ts +78 -0
- package/src/objects.ts +180 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @mantiq/helpers
|
|
2
|
+
|
|
3
|
+
Utility library for MantiqJS — Str, Arr, Num, Collection, HTTP client, and async utilities.
|
|
4
|
+
|
|
5
|
+
Part of [MantiqJS](https://github.com/abdullahkhan/mantiq) — a batteries-included TypeScript web framework for Bun.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @mantiq/helpers
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Documentation
|
|
14
|
+
|
|
15
|
+
See the [MantiqJS repository](https://github.com/abdullahkhan/mantiq) for full documentation.
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mantiq/helpers",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Str, Arr, Num, Collection utilities",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Abdullah Khan",
|
|
8
|
+
"homepage": "https://github.com/abdullahkhan/mantiq/tree/main/packages/helpers",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/abdullahkhan/mantiq.git",
|
|
12
|
+
"directory": "packages/helpers"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/abdullahkhan/mantiq/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mantiq",
|
|
19
|
+
"mantiqjs",
|
|
20
|
+
"bun",
|
|
21
|
+
"typescript",
|
|
22
|
+
"framework",
|
|
23
|
+
"helpers"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.1.0"
|
|
27
|
+
},
|
|
28
|
+
"main": "./src/index.ts",
|
|
29
|
+
"types": "./src/index.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"bun": "./src/index.ts",
|
|
33
|
+
"default": "./src/index.ts"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src/",
|
|
38
|
+
"package.json",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
44
|
+
"test": "bun test",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"clean": "rm -rf dist"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"bun-types": "latest",
|
|
50
|
+
"typescript": "^5.7.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/Arr.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array utility functions with deep dot-notation support.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* Arr.get({ user: { name: 'John' } }, 'user.name') // 'John'
|
|
7
|
+
* Arr.pluck([{ id: 1 }, { id: 2 }], 'id') // [1, 2]
|
|
8
|
+
* Arr.groupBy([1, 2, 3, 4], (n) => n % 2 === 0 ? 'even' : 'odd')
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export const Arr = {
|
|
12
|
+
/** Wrap a value in an array if it isn't one already */
|
|
13
|
+
wrap<T>(value: T | T[] | null | undefined): T[] {
|
|
14
|
+
if (value === null || value === undefined) return []
|
|
15
|
+
return Array.isArray(value) ? value : [value]
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/** Flatten a nested array to a given depth (default: Infinity) */
|
|
19
|
+
flatten<T>(array: any[], depth = Infinity): T[] {
|
|
20
|
+
return array.flat(depth) as T[]
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/** Split an array into chunks of a given size */
|
|
24
|
+
chunk<T>(array: T[], size: number): T[][] {
|
|
25
|
+
const result: T[][] = []
|
|
26
|
+
for (let i = 0; i < array.length; i += size) {
|
|
27
|
+
result.push(array.slice(i, i + size))
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/** Get a value from a nested object using dot notation */
|
|
33
|
+
get<T = any>(obj: any, path: string, defaultValue?: T): T {
|
|
34
|
+
const keys = path.split('.')
|
|
35
|
+
let result = obj
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (result === null || result === undefined) return defaultValue as T
|
|
38
|
+
result = result[key]
|
|
39
|
+
}
|
|
40
|
+
return (result === undefined ? defaultValue : result) as T
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/** Set a value on a nested object using dot notation (mutates) */
|
|
44
|
+
set(obj: any, path: string, value: any): void {
|
|
45
|
+
const keys = path.split('.')
|
|
46
|
+
let current = obj
|
|
47
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
48
|
+
const key = keys[i]!
|
|
49
|
+
if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
|
|
50
|
+
current[key] = {}
|
|
51
|
+
}
|
|
52
|
+
current = current[key]
|
|
53
|
+
}
|
|
54
|
+
current[keys[keys.length - 1]!] = value
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/** Check if a key exists at a dot-notated path */
|
|
58
|
+
has(obj: any, path: string): boolean {
|
|
59
|
+
const keys = path.split('.')
|
|
60
|
+
let current = obj
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
if (current === null || current === undefined || typeof current !== 'object') return false
|
|
63
|
+
if (!(key in current)) return false
|
|
64
|
+
current = current[key]
|
|
65
|
+
}
|
|
66
|
+
return true
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/** Remove a key at a dot-notated path (mutates) */
|
|
70
|
+
forget(obj: any, path: string): void {
|
|
71
|
+
const keys = path.split('.')
|
|
72
|
+
let current = obj
|
|
73
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
74
|
+
const key = keys[i]!
|
|
75
|
+
if (current[key] === undefined || typeof current[key] !== 'object') return
|
|
76
|
+
current = current[key]
|
|
77
|
+
}
|
|
78
|
+
delete current[keys[keys.length - 1]!]
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/** Flatten a nested object into dot-notation keys */
|
|
82
|
+
dot(obj: Record<string, any>, prefix = ''): Record<string, any> {
|
|
83
|
+
const result: Record<string, any> = {}
|
|
84
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
85
|
+
const fullKey = prefix ? `${prefix}.${key}` : key
|
|
86
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
87
|
+
Object.assign(result, Arr.dot(value, fullKey))
|
|
88
|
+
} else {
|
|
89
|
+
result[fullKey] = value
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/** Expand a dot-notation object into a nested object */
|
|
96
|
+
undot(obj: Record<string, any>): Record<string, any> {
|
|
97
|
+
const result: Record<string, any> = {}
|
|
98
|
+
for (const [path, value] of Object.entries(obj)) {
|
|
99
|
+
Arr.set(result, path, value)
|
|
100
|
+
}
|
|
101
|
+
return result
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/** Pluck a single property from each item. With 3 args, creates a keyed object. */
|
|
105
|
+
pluck<T>(array: Record<string, any>[], valueKey: string, keyKey?: string): T[] | Record<string, T> {
|
|
106
|
+
if (keyKey !== undefined) {
|
|
107
|
+
const result: Record<string, T> = {}
|
|
108
|
+
for (const item of array) {
|
|
109
|
+
result[Arr.get(item, keyKey)] = Arr.get(item, valueKey)
|
|
110
|
+
}
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
113
|
+
return array.map((item) => Arr.get<T>(item, valueKey))
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/** Key an array by a given property */
|
|
117
|
+
keyBy<T extends Record<string, any>>(array: T[], key: string | ((item: T) => string)): Record<string, T> {
|
|
118
|
+
const result: Record<string, T> = {}
|
|
119
|
+
for (const item of array) {
|
|
120
|
+
const k = typeof key === 'function' ? key(item) : Arr.get(item, key)
|
|
121
|
+
result[k] = item
|
|
122
|
+
}
|
|
123
|
+
return result
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/** Group an array by a key or callback */
|
|
127
|
+
groupBy<T>(array: T[], key: string | ((item: T) => string)): Record<string, T[]> {
|
|
128
|
+
const result: Record<string, T[]> = {}
|
|
129
|
+
for (const item of array) {
|
|
130
|
+
const k = typeof key === 'function' ? key(item) : Arr.get(item as any, key)
|
|
131
|
+
if (!result[k]) result[k] = []
|
|
132
|
+
result[k].push(item)
|
|
133
|
+
}
|
|
134
|
+
return result
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/** Sort by a key or callback */
|
|
138
|
+
sortBy<T>(array: T[], key: string | ((item: T) => any)): T[] {
|
|
139
|
+
return [...array].sort((a, b) => {
|
|
140
|
+
const va = typeof key === 'function' ? key(a) : Arr.get(a as any, key)
|
|
141
|
+
const vb = typeof key === 'function' ? key(b) : Arr.get(b as any, key)
|
|
142
|
+
if (va < vb) return -1
|
|
143
|
+
if (va > vb) return 1
|
|
144
|
+
return 0
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/** Sort by a key in descending order */
|
|
149
|
+
sortByDesc<T>(array: T[], key: string | ((item: T) => any)): T[] {
|
|
150
|
+
return Arr.sortBy(array, key).reverse()
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/** Get unique values */
|
|
154
|
+
unique<T>(array: T[], key?: string | ((item: T) => any)): T[] {
|
|
155
|
+
if (!key) return [...new Set(array)]
|
|
156
|
+
const seen = new Set()
|
|
157
|
+
return array.filter((item) => {
|
|
158
|
+
const k = typeof key === 'function' ? key(item) : Arr.get(item as any, key)
|
|
159
|
+
if (seen.has(k)) return false
|
|
160
|
+
seen.add(k)
|
|
161
|
+
return true
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/** Shuffle an array (Fisher-Yates) */
|
|
166
|
+
shuffle<T>(array: T[]): T[] {
|
|
167
|
+
const result = [...array]
|
|
168
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
169
|
+
const j = Math.floor(Math.random() * (i + 1))
|
|
170
|
+
;[result[i], result[j]] = [result[j]!, result[i]!]
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/** Get the first element matching a predicate (or the first element) */
|
|
176
|
+
first<T>(array: T[], predicate?: (item: T) => boolean): T | undefined {
|
|
177
|
+
if (!predicate) return array[0]
|
|
178
|
+
return array.find(predicate)
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/** Get the last element matching a predicate (or the last element) */
|
|
182
|
+
last<T>(array: T[], predicate?: (item: T) => boolean): T | undefined {
|
|
183
|
+
if (!predicate) return array[array.length - 1]
|
|
184
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
185
|
+
if (predicate(array[i]!)) return array[i]
|
|
186
|
+
}
|
|
187
|
+
return undefined
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/** Get a random element from the array */
|
|
191
|
+
random<T>(array: T[]): T | undefined {
|
|
192
|
+
if (array.length === 0) return undefined
|
|
193
|
+
return array[Math.floor(Math.random() * array.length)]
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/** Pick only the given keys from each object */
|
|
197
|
+
only<T extends Record<string, any>>(obj: T, keys: string[]): Partial<T> {
|
|
198
|
+
const result: any = {}
|
|
199
|
+
for (const key of keys) {
|
|
200
|
+
if (key in obj) result[key] = obj[key]
|
|
201
|
+
}
|
|
202
|
+
return result
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
/** Get all keys except the given ones */
|
|
206
|
+
except<T extends Record<string, any>>(obj: T, keys: string[]): Partial<T> {
|
|
207
|
+
const excluded = new Set(keys)
|
|
208
|
+
const result: any = {}
|
|
209
|
+
for (const key of Object.keys(obj)) {
|
|
210
|
+
if (!excluded.has(key)) result[key] = obj[key]
|
|
211
|
+
}
|
|
212
|
+
return result
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/** Partition an array into two based on a predicate */
|
|
216
|
+
partition<T>(array: T[], predicate: (item: T) => boolean): [T[], T[]] {
|
|
217
|
+
const pass: T[] = []
|
|
218
|
+
const fail: T[] = []
|
|
219
|
+
for (const item of array) {
|
|
220
|
+
if (predicate(item)) pass.push(item)
|
|
221
|
+
else fail.push(item)
|
|
222
|
+
}
|
|
223
|
+
return [pass, fail]
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/** Create a cross-join of multiple arrays */
|
|
227
|
+
crossJoin<T>(...arrays: T[][]): T[][] {
|
|
228
|
+
return arrays.reduce<T[][]>(
|
|
229
|
+
(acc, arr) => acc.flatMap((combo) => arr.map((item) => [...combo, item])),
|
|
230
|
+
[[]],
|
|
231
|
+
)
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/** Count occurrences by a callback */
|
|
235
|
+
countBy<T>(array: T[], key: string | ((item: T) => string)): Record<string, number> {
|
|
236
|
+
const result: Record<string, number> = {}
|
|
237
|
+
for (const item of array) {
|
|
238
|
+
const k = typeof key === 'function' ? key(item) : Arr.get(item as any, key)
|
|
239
|
+
result[k] = (result[k] ?? 0) + 1
|
|
240
|
+
}
|
|
241
|
+
return result
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/** Zip multiple arrays together */
|
|
245
|
+
zip<T>(...arrays: T[][]): T[][] {
|
|
246
|
+
const maxLen = Math.max(...arrays.map((a) => a.length))
|
|
247
|
+
const result: T[][] = []
|
|
248
|
+
for (let i = 0; i < maxLen; i++) {
|
|
249
|
+
result.push(arrays.map((a) => a[i]!))
|
|
250
|
+
}
|
|
251
|
+
return result
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/** Create a range of numbers */
|
|
255
|
+
range(start: number, end: number, step = 1): number[] {
|
|
256
|
+
const result: number[] = []
|
|
257
|
+
if (step > 0) {
|
|
258
|
+
for (let i = start; i <= end; i += step) result.push(i)
|
|
259
|
+
} else {
|
|
260
|
+
for (let i = start; i >= end; i += step) result.push(i)
|
|
261
|
+
}
|
|
262
|
+
return result
|
|
263
|
+
},
|
|
264
|
+
}
|