@leancodepl/utils 8.5.0 → 8.6.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.
- package/README.md +465 -0
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +424 -0
- package/index.cjs.mjs +2 -0
- package/index.d.ts +1 -0
- package/index.esm.js +406 -0
- package/package.json +2 -7
- package/src/index.d.ts +16 -0
- package/src/lib/addPrefix.d.ts +20 -0
- package/src/lib/assertDefined.d.ts +17 -0
- package/src/lib/assertNotEmpty.d.ts +17 -0
- package/src/lib/assertNotNull.d.ts +17 -0
- package/src/lib/downloadFile.d.ts +29 -0
- package/src/lib/ensureDefined.d.ts +17 -0
- package/src/lib/ensureNotEmpty.d.ts +17 -0
- package/src/lib/ensureNotNull.d.ts +18 -0
- package/src/lib/hooks/useBoundRunInTask.d.ts +30 -0
- package/src/lib/hooks/useDialog.d.ts +27 -0
- package/src/lib/hooks/useKeyByRoute.d.ts +29 -0
- package/src/lib/hooks/useRunInTask.d.ts +25 -0
- package/src/lib/hooks/useSetUnset.d.ts +23 -0
- package/src/lib/transformDeep.d.ts +29 -0
- package/src/lib/transformFirst.d.ts +24 -0
- package/src/lib/types/index.d.ts +2 -0
- package/src/lib/types/transformDeep.d.ts +10 -0
- package/src/lib/types/unpromisify.d.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# @leancodepl/utils
|
|
2
|
+
|
|
3
|
+
A TypeScript utility library for common development tasks including assertions, object transformations, file operations,
|
|
4
|
+
and hooks.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @leancodepl/utils
|
|
10
|
+
# or
|
|
11
|
+
yarn add @leancodepl/utils
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## API
|
|
15
|
+
|
|
16
|
+
### `assertDefined(value, message)`
|
|
17
|
+
|
|
18
|
+
Asserts that a value is not undefined. Throws an error if the value is undefined.
|
|
19
|
+
|
|
20
|
+
**Parameters:**
|
|
21
|
+
|
|
22
|
+
- `value: T | undefined` - The value to check for undefined
|
|
23
|
+
- `message?: string` - Optional error message to use if assertion fails
|
|
24
|
+
|
|
25
|
+
**Usage:**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { assertDefined } from "@leancodepl/utils"
|
|
29
|
+
|
|
30
|
+
interface User {
|
|
31
|
+
name: string
|
|
32
|
+
email: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function processUserAssert(user?: User) {
|
|
36
|
+
assertDefined(user) // Throws if undefined, no return value
|
|
37
|
+
return user.name // TypeScript knows user is defined
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `assertNotNull(value, message)`
|
|
42
|
+
|
|
43
|
+
Asserts that a value is not null. Throws an error if the value is null.
|
|
44
|
+
|
|
45
|
+
**Parameters:**
|
|
46
|
+
|
|
47
|
+
- `value: T | null` - The value to check for null
|
|
48
|
+
- `message?: string` - Optional error message to use if assertion fails
|
|
49
|
+
|
|
50
|
+
**Usage:**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { assertNotNull } from "@leancodepl/utils"
|
|
54
|
+
|
|
55
|
+
interface User {
|
|
56
|
+
name: string
|
|
57
|
+
email: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function processData(data: string | null) {
|
|
61
|
+
assertNotNull(data, "Data cannot be null")
|
|
62
|
+
return data.toUpperCase() // TypeScript knows data is string
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `assertNotEmpty(value, message)`
|
|
67
|
+
|
|
68
|
+
Asserts that a value is not null or undefined. Throws an error if the value is null or undefined.
|
|
69
|
+
|
|
70
|
+
**Parameters:**
|
|
71
|
+
|
|
72
|
+
- `value: T | null | undefined` - The value to check for null or undefined
|
|
73
|
+
- `message?: string` - Optional error message to use if assertion fails
|
|
74
|
+
|
|
75
|
+
**Usage:**
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { assertNotEmpty } from "@leancodepl/utils"
|
|
79
|
+
|
|
80
|
+
interface User {
|
|
81
|
+
name: string
|
|
82
|
+
email: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function processValue(value: string | null | undefined) {
|
|
86
|
+
assertNotEmpty(value, "Value is required")
|
|
87
|
+
return value.length // TypeScript knows value is string
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `ensureDefined(value, message)`
|
|
92
|
+
|
|
93
|
+
Ensures that a value is defined, returning it if defined or throwing an error if undefined.
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
|
|
97
|
+
- `value: T | undefined` - The value to ensure is defined
|
|
98
|
+
- `message?: string` - Optional error message to use if the value is undefined
|
|
99
|
+
|
|
100
|
+
**Returns:** The value if it is defined
|
|
101
|
+
|
|
102
|
+
**Usage:**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { ensureDefined } from "@leancodepl/utils"
|
|
106
|
+
|
|
107
|
+
interface User {
|
|
108
|
+
name: string
|
|
109
|
+
email: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function processUser(user?: User) {
|
|
113
|
+
const definedUser = ensureDefined(user) // Returns User or throws
|
|
114
|
+
return definedUser.name
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `ensureNotNull(value, message)`
|
|
119
|
+
|
|
120
|
+
Ensures that a value is not null, returning it if not null or throwing an error if null.
|
|
121
|
+
|
|
122
|
+
**Parameters:**
|
|
123
|
+
|
|
124
|
+
- `value: T | null` - The value to ensure is not null
|
|
125
|
+
- `message?: string` - Optional error message to use if the value is null
|
|
126
|
+
|
|
127
|
+
**Returns:** The value if it is not null
|
|
128
|
+
|
|
129
|
+
**Usage:**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { ensureNotNull } from "@leancodepl/utils"
|
|
133
|
+
|
|
134
|
+
interface User {
|
|
135
|
+
name: string
|
|
136
|
+
email: string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function processData(data: string | null) {
|
|
140
|
+
const validData = ensureNotNull(data, "Data cannot be null")
|
|
141
|
+
return validData.toUpperCase()
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `ensureNotEmpty(value, message)`
|
|
146
|
+
|
|
147
|
+
Ensures that a value is not null or undefined, returning it if valid or throwing an error if empty.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
|
|
151
|
+
- `value: T | null | undefined` - The value to ensure is not null or undefined
|
|
152
|
+
- `message?: string` - Optional error message to use if the value is null or undefined
|
|
153
|
+
|
|
154
|
+
**Returns:** The value if it is not null or undefined
|
|
155
|
+
|
|
156
|
+
**Usage:**
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { ensureNotEmpty } from "@leancodepl/utils"
|
|
160
|
+
|
|
161
|
+
interface User {
|
|
162
|
+
name: string
|
|
163
|
+
email: string
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function processValue(value: string | null | undefined) {
|
|
167
|
+
const validValue = ensureNotEmpty(value, "Value is required")
|
|
168
|
+
return validValue.length
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `addPrefix(object, prefix)`
|
|
173
|
+
|
|
174
|
+
Adds a prefix to all keys in an object, creating a new object with prefixed keys.
|
|
175
|
+
|
|
176
|
+
**Parameters:**
|
|
177
|
+
|
|
178
|
+
- `object: T extends object` - The object whose keys will be prefixed
|
|
179
|
+
- `prefix: TPrefix extends string` - The prefix string to add to each key
|
|
180
|
+
|
|
181
|
+
**Returns:** A new object with all keys prefixed
|
|
182
|
+
|
|
183
|
+
**Usage:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { addPrefix } from "@leancodepl/utils"
|
|
187
|
+
|
|
188
|
+
const apiData = { userId: 1, userName: "John" }
|
|
189
|
+
const prefixed = addPrefix(apiData, "api_")
|
|
190
|
+
// Result: { api_userId: 1, api_userName: 'John' }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `capitalizeDeep(value)`
|
|
194
|
+
|
|
195
|
+
Recursively transforms all object keys to use capitalized (PascalCase) format.
|
|
196
|
+
|
|
197
|
+
**Parameters:**
|
|
198
|
+
|
|
199
|
+
- `value: T` - The value to transform (can be object, array, or primitive)
|
|
200
|
+
|
|
201
|
+
**Returns:** A new object with all keys converted to PascalCase
|
|
202
|
+
|
|
203
|
+
**Usage:**
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { capitalizeDeep } from "@leancodepl/utils"
|
|
207
|
+
|
|
208
|
+
const clientData = { userId: 1, userName: "John", profile: { firstName: "John" } }
|
|
209
|
+
const serverData = capitalizeDeep(clientData)
|
|
210
|
+
// Result: { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### `uncapitalizeDeep(value)`
|
|
214
|
+
|
|
215
|
+
Recursively transforms all object keys to use uncapitalized (camelCase) format.
|
|
216
|
+
|
|
217
|
+
**Parameters:**
|
|
218
|
+
|
|
219
|
+
- `value: T` - The value to transform (can be object, array, or primitive)
|
|
220
|
+
|
|
221
|
+
**Returns:** A new object with all keys converted to camelCase
|
|
222
|
+
|
|
223
|
+
**Usage:**
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { uncapitalizeDeep } from "@leancodepl/utils"
|
|
227
|
+
|
|
228
|
+
const serverData = { UserId: 1, UserName: "John", Profile: { FirstName: "John" } }
|
|
229
|
+
const clientData = uncapitalizeDeep(serverData)
|
|
230
|
+
// Result: { userId: 1, userName: 'John', profile: { firstName: 'John' } }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### `toLowerFirst(value)`
|
|
234
|
+
|
|
235
|
+
Converts the first character of a string to lowercase.
|
|
236
|
+
|
|
237
|
+
**Parameters:**
|
|
238
|
+
|
|
239
|
+
- `value: string` - The string to transform
|
|
240
|
+
|
|
241
|
+
**Returns:** The string with the first character in lowercase
|
|
242
|
+
|
|
243
|
+
**Usage:**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { toLowerFirst } from "@leancodepl/utils"
|
|
247
|
+
|
|
248
|
+
const result = toLowerFirst("HelloWorld")
|
|
249
|
+
// Result: 'helloWorld'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `toUpperFirst(value)`
|
|
253
|
+
|
|
254
|
+
Converts the first character of a string to uppercase.
|
|
255
|
+
|
|
256
|
+
**Parameters:**
|
|
257
|
+
|
|
258
|
+
- `value: string` - The string to transform
|
|
259
|
+
|
|
260
|
+
**Returns:** The string with the first character in uppercase
|
|
261
|
+
|
|
262
|
+
**Usage:**
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { toUpperFirst } from "@leancodepl/utils"
|
|
266
|
+
|
|
267
|
+
const result = toUpperFirst("helloWorld")
|
|
268
|
+
// Result: 'HelloWorld'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### `downloadFile(dataOrUrl, options)`
|
|
272
|
+
|
|
273
|
+
Download a file from URL or Blob object.
|
|
274
|
+
|
|
275
|
+
**Parameters:**
|
|
276
|
+
|
|
277
|
+
- `url: string` OR `obj: Blob | MediaSource` - The URL to download from or the Blob/MediaSource object
|
|
278
|
+
- `options?: DownloadFileOptions` - Optional download options
|
|
279
|
+
|
|
280
|
+
**Usage:**
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { downloadFile } from "@leancodepl/utils"
|
|
284
|
+
|
|
285
|
+
// Download from URL
|
|
286
|
+
downloadFile("https://example.com/file.pdf", { name: "document.pdf" })
|
|
287
|
+
|
|
288
|
+
// Download from Blob
|
|
289
|
+
const blob = new Blob(["Hello World"], { type: "text/plain" })
|
|
290
|
+
downloadFile(blob, { name: "hello.txt" })
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### `useDialog(onAfterClose)`
|
|
294
|
+
|
|
295
|
+
React hook for managing dialog state with optional callback after closing. Provides convenient open/close functions and
|
|
296
|
+
tracks the dialog's open state.
|
|
297
|
+
|
|
298
|
+
**Parameters:**
|
|
299
|
+
|
|
300
|
+
- `onAfterClose?: () => void` - Optional callback function to execute after the dialog closes
|
|
301
|
+
|
|
302
|
+
**Returns:** Object containing `{ isDialogOpen: boolean, openDialog: () => void, closeDialog: () => void }`
|
|
303
|
+
|
|
304
|
+
**Usage:**
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import React from 'react';
|
|
308
|
+
import { useDialog } from '@leancodepl/utils';
|
|
309
|
+
|
|
310
|
+
function MyComponent() {
|
|
311
|
+
const { isDialogOpen, openDialog, closeDialog } = useDialog(() => {
|
|
312
|
+
console.log('Dialog closed');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<div>
|
|
317
|
+
<button onClick={openDialog}>Open Dialog</button>
|
|
318
|
+
{isDialogOpen && (
|
|
319
|
+
<div className="dialog">
|
|
320
|
+
<p>Dialog content</p>
|
|
321
|
+
<button onClick={closeDialog}>Close</button>
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `useRunInTask()`
|
|
330
|
+
|
|
331
|
+
React hook for tracking async task execution with loading state. Automatically manages a loading counter and provides a
|
|
332
|
+
wrapper function for tasks.
|
|
333
|
+
|
|
334
|
+
**Returns:** A tuple containing `[isLoading: boolean, runInTask: function]`
|
|
335
|
+
|
|
336
|
+
**Usage:**
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import React from 'react';
|
|
340
|
+
import { useRunInTask } from '@leancodepl/utils';
|
|
341
|
+
|
|
342
|
+
async function saveData() {
|
|
343
|
+
console.log('Saving data...');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function MyComponent() {
|
|
347
|
+
const [isLoading, runInTask] = useRunInTask();
|
|
348
|
+
|
|
349
|
+
const handleSave = async () => {
|
|
350
|
+
await runInTask(async () => {
|
|
351
|
+
await saveData();
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<button onClick={handleSave} disabled={isLoading}>
|
|
357
|
+
{isLoading ? 'Saving...' : 'Save'}
|
|
358
|
+
</button>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### `useBoundRunInTask(block)`
|
|
364
|
+
|
|
365
|
+
React hook for bound task execution with loading state. Creates a wrapped version of a function that automatically
|
|
366
|
+
tracks loading state.
|
|
367
|
+
|
|
368
|
+
**Parameters:**
|
|
369
|
+
|
|
370
|
+
- `block: T | undefined` - The function to wrap with task tracking
|
|
371
|
+
|
|
372
|
+
**Returns:** A tuple containing `[isLoading: boolean, wrappedFunction: T]`
|
|
373
|
+
|
|
374
|
+
**Usage:**
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import React, { useState, useEffect } from 'react';
|
|
378
|
+
import { useBoundRunInTask } from '@leancodepl/utils';
|
|
379
|
+
|
|
380
|
+
interface User {
|
|
381
|
+
name: string;
|
|
382
|
+
email: string;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function fetchUser(userId: string): Promise<User> {
|
|
386
|
+
return { name: 'John Doe', email: 'john@example.com' };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
390
|
+
const [user, setUser] = useState<User | null>(null);
|
|
391
|
+
|
|
392
|
+
const [isLoading, loadUser] = useBoundRunInTask(async () => {
|
|
393
|
+
const userData = await fetchUser(userId);
|
|
394
|
+
setUser(userData);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
loadUser();
|
|
399
|
+
}, [userId, loadUser]);
|
|
400
|
+
|
|
401
|
+
if (isLoading) return <div>Loading...</div>;
|
|
402
|
+
return <div>{user?.name}</div>;
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### `useKeyByRoute(routeMatches)`
|
|
407
|
+
|
|
408
|
+
React hook for generating keys based on current route matches.
|
|
409
|
+
|
|
410
|
+
**Parameters:**
|
|
411
|
+
|
|
412
|
+
- `routeMatches: Record<TKey, (object | null)[] | never | object | null>` - Record of route keys to match objects or
|
|
413
|
+
arrays
|
|
414
|
+
|
|
415
|
+
**Returns:** Array of active route keys
|
|
416
|
+
|
|
417
|
+
**Usage:**
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { useKeyByRoute } from '@leancodepl/utils';
|
|
421
|
+
|
|
422
|
+
function MyComponent() {
|
|
423
|
+
const routeKeys = useKeyByRoute({
|
|
424
|
+
home: { path: '/' },
|
|
425
|
+
profile: { path: '/profile' },
|
|
426
|
+
settings: null
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<div>
|
|
431
|
+
Active routes: {routeKeys.join(', ')}
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### `useSetUnset(set)`
|
|
438
|
+
|
|
439
|
+
React hook for boolean state management helpers.
|
|
440
|
+
|
|
441
|
+
**Parameters:**
|
|
442
|
+
|
|
443
|
+
- `set: Dispatch<SetStateAction<boolean>>` - The state setter function from useState
|
|
444
|
+
|
|
445
|
+
**Returns:** A tuple containing `[setTrue: function, setFalse: function]`
|
|
446
|
+
|
|
447
|
+
**Usage:**
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
import React, { useState } from 'react';
|
|
451
|
+
import { useSetUnset } from '@leancodepl/utils';
|
|
452
|
+
|
|
453
|
+
function MyComponent() {
|
|
454
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
455
|
+
const [show, hide] = useSetUnset(setIsVisible);
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<div>
|
|
459
|
+
<button onClick={show}>Show</button>
|
|
460
|
+
<button onClick={hide}>Hide</button>
|
|
461
|
+
{isVisible && <div>Visible content</div>}
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
exports._default = require('./index.cjs.js').default;
|