@leancodepl/utils 9.7.2 → 9.7.4

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 (57) hide show
  1. package/CHANGELOG.md +693 -0
  2. package/LICENSE +201 -0
  3. package/dist/index.d.ts +14 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +146 -0
  6. package/dist/index.umd.cjs +1 -0
  7. package/{src → dist}/lib/addPrefix.d.ts +1 -0
  8. package/dist/lib/addPrefix.d.ts.map +1 -0
  9. package/{src → dist}/lib/assertDefined.d.ts +1 -0
  10. package/dist/lib/assertDefined.d.ts.map +1 -0
  11. package/{src → dist}/lib/assertNotEmpty.d.ts +1 -0
  12. package/dist/lib/assertNotEmpty.d.ts.map +1 -0
  13. package/{src → dist}/lib/assertNotNull.d.ts +1 -0
  14. package/dist/lib/assertNotNull.d.ts.map +1 -0
  15. package/{src → dist}/lib/downloadFile.d.ts +1 -0
  16. package/dist/lib/downloadFile.d.ts.map +1 -0
  17. package/{src → dist}/lib/ensureDefined.d.ts +1 -0
  18. package/dist/lib/ensureDefined.d.ts.map +1 -0
  19. package/{src → dist}/lib/ensureNotEmpty.d.ts +1 -0
  20. package/dist/lib/ensureNotEmpty.d.ts.map +1 -0
  21. package/{src → dist}/lib/ensureNotNull.d.ts +1 -0
  22. package/dist/lib/ensureNotNull.d.ts.map +1 -0
  23. package/dist/lib/hooks/index.d.ts +7 -0
  24. package/dist/lib/hooks/index.d.ts.map +1 -0
  25. package/{src → dist}/lib/hooks/useBoundRunInTask.d.ts +1 -0
  26. package/dist/lib/hooks/useBoundRunInTask.d.ts.map +1 -0
  27. package/{src → dist}/lib/hooks/useDialog.d.ts +1 -0
  28. package/dist/lib/hooks/useDialog.d.ts.map +1 -0
  29. package/{src → dist}/lib/hooks/useKeyByRoute.d.ts +1 -0
  30. package/dist/lib/hooks/useKeyByRoute.d.ts.map +1 -0
  31. package/{src → dist}/lib/hooks/useRunInTask.d.ts +1 -0
  32. package/dist/lib/hooks/useRunInTask.d.ts.map +1 -0
  33. package/{src → dist}/lib/hooks/useSetUnset.d.ts +2 -1
  34. package/dist/lib/hooks/useSetUnset.d.ts.map +1 -0
  35. package/{src → dist}/lib/hooks/useSyncState.d.ts +1 -0
  36. package/dist/lib/hooks/useSyncState.d.ts.map +1 -0
  37. package/{src → dist}/lib/transformDeep.d.ts +2 -1
  38. package/dist/lib/transformDeep.d.ts.map +1 -0
  39. package/{src → dist}/lib/transformFirst.d.ts +1 -0
  40. package/dist/lib/transformFirst.d.ts.map +1 -0
  41. package/dist/lib/types/index.d.ts +3 -0
  42. package/dist/lib/types/index.d.ts.map +1 -0
  43. package/{src → dist}/lib/types/transformDeep.d.ts +2 -1
  44. package/dist/lib/types/transformDeep.d.ts.map +1 -0
  45. package/{src → dist}/lib/types/unpromisify.d.ts +1 -0
  46. package/dist/lib/types/unpromisify.d.ts.map +1 -0
  47. package/{src → dist}/lib/valueContext.d.ts +2 -1
  48. package/dist/lib/valueContext.d.ts.map +1 -0
  49. package/package.json +20 -15
  50. package/index.cjs.default.js +0 -1
  51. package/index.cjs.js +0 -521
  52. package/index.cjs.mjs +0 -2
  53. package/index.d.ts +0 -1
  54. package/index.esm.js +0 -501
  55. package/src/index.d.ts +0 -13
  56. package/src/lib/hooks/index.d.ts +0 -6
  57. package/src/lib/types/index.d.ts +0 -2
package/package.json CHANGED
@@ -1,16 +1,28 @@
1
1
  {
2
2
  "name": "@leancodepl/utils",
3
- "version": "9.7.2",
3
+ "version": "9.7.4",
4
4
  "license": "Apache-2.0",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ "./package.json": "./package.json",
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
5
17
  "publishConfig": {
6
18
  "access": "public",
7
19
  "registry": "https://registry.npmjs.org/"
8
20
  },
9
21
  "engines": {
10
- "node": ">=18.0.0"
22
+ "node": ">=22.0.0"
11
23
  },
12
24
  "dependencies": {
13
- "@leancodepl/api-date": "9.7.2",
25
+ "@leancodepl/api-date": "9.7.4",
14
26
  "tiny-invariant": ">=1.3.1"
15
27
  },
16
28
  "peerDependencies": {
@@ -40,16 +52,9 @@
40
52
  "url": "https://leancode.co"
41
53
  },
42
54
  "sideEffects": false,
43
- "exports": {
44
- "./package.json": "./package.json",
45
- ".": {
46
- "module": "./index.esm.js",
47
- "types": "./index.d.ts",
48
- "import": "./index.cjs.mjs",
49
- "default": "./index.cjs.js"
50
- }
51
- },
52
- "module": "./index.esm.js",
53
- "main": "./index.cjs.js",
54
- "types": "./index.d.ts"
55
+ "files": [
56
+ "dist",
57
+ "CHANGELOG.md",
58
+ "!**/*.tsbuildinfo"
59
+ ]
55
60
  }
@@ -1 +0,0 @@
1
- exports._default = require('./index.cjs.js').default;
package/index.cjs.js DELETED
@@ -1,521 +0,0 @@
1
- 'use strict';
2
-
3
- var react = require('react');
4
- var invariant = require('tiny-invariant');
5
- var jsxRuntime = require('react/jsx-runtime');
6
-
7
- /**
8
- * React hook for tracking async task execution with loading state.
9
- * Automatically manages a loading counter and provides a wrapper function for tasks.
10
- *
11
- * @returns A tuple containing [isLoading: boolean, runInTask: function]
12
- * @example
13
- * ```typescript
14
- * function MyComponent() {
15
- * const [isLoading, runInTask] = useRunInTask();
16
- *
17
- * const handleSave = async () => {
18
- * await runInTask(async () => {
19
- * await saveData();
20
- * });
21
- * };
22
- *
23
- * return (
24
- * <button onClick={handleSave} disabled={isLoading}>
25
- * {isLoading ? 'Saving...' : 'Save'}
26
- * </button>
27
- * );
28
- * }
29
- * ```
30
- */ function useRunInTask() {
31
- const [runningTasks, setRunningTasks] = react.useState(0);
32
- const runInTask = react.useCallback(async (task)=>{
33
- setRunningTasks((runningTasks)=>runningTasks + 1);
34
- try {
35
- return await task();
36
- } finally{
37
- setRunningTasks((runningTasks)=>runningTasks - 1);
38
- }
39
- }, []);
40
- return [
41
- runningTasks > 0,
42
- runInTask
43
- ];
44
- }
45
-
46
- function useBoundRunInTask(block) {
47
- const [isRunning, runInTask] = useRunInTask();
48
- const runBlockInTask = react.useMemo(()=>block ? (...args)=>runInTask(()=>block(...args)) : undefined, [
49
- block,
50
- runInTask
51
- ]);
52
- return [
53
- isRunning,
54
- runBlockInTask
55
- ];
56
- }
57
-
58
- /**
59
- * React hook for boolean state management helpers.
60
- *
61
- * @param set - The state setter function from useState
62
- * @returns A tuple containing [setTrue: function, setFalse: function]
63
- * @example
64
- * ```typescript
65
- * function MyComponent() {
66
- * const [isVisible, setIsVisible] = useState(false);
67
- * const [show, hide] = useSetUnset(setIsVisible);
68
- *
69
- * return (
70
- * <div>
71
- * <button onClick={show}>Show</button>
72
- * <button onClick={hide}>Hide</button>
73
- * {isVisible && <div>Content is visible</div>}
74
- * </div>
75
- * );
76
- * }
77
- * ```
78
- */ function useSetUnset(set) {
79
- return [
80
- react.useCallback(()=>set(true), [
81
- set
82
- ]),
83
- react.useCallback(()=>set(false), [
84
- set
85
- ])
86
- ];
87
- }
88
-
89
- /**
90
- * React hook for managing dialog state with optional callback after closing.
91
- * Provides convenient open/close functions and tracks the dialog's open state.
92
- *
93
- * @param onAfterClose - Optional callback function to execute after the dialog closes
94
- * @returns Object containing dialog state and control functions
95
- * @example
96
- * ```typescript
97
- * function MyComponent() {
98
- * const { isDialogOpen, openDialog, closeDialog } = useDialog(() => {
99
- * console.log('Dialog closed');
100
- * });
101
- *
102
- * return (
103
- * <div>
104
- * <button onClick={openDialog}>Open Dialog</button>
105
- * {isDialogOpen && <Dialog onClose={closeDialog} />}
106
- * </div>
107
- * );
108
- * }
109
- * ```
110
- */ function useDialog(onAfterClose) {
111
- const [isDialogOpen, setIsDialogOpen] = react.useState(false);
112
- const [openDialog, closeDialog] = useSetUnset(setIsDialogOpen);
113
- const close = react.useCallback(()=>{
114
- closeDialog();
115
- if (onAfterClose) setTimeout(onAfterClose);
116
- }, [
117
- closeDialog,
118
- onAfterClose
119
- ]);
120
- return {
121
- isDialogOpen,
122
- openDialog,
123
- closeDialog: close
124
- };
125
- }
126
-
127
- /**
128
- * React hook for generating keys based on current route matches.
129
- *
130
- * @template TKey - The type of the route keys
131
- * @param routeMatches - Record of route keys to match objects or arrays
132
- * @returns Array of active route keys
133
- * @example
134
- * ```typescript
135
- * function NavigationComponent() {
136
- * const routeMatches = {
137
- * home: useRouteMatch('/'),
138
- * about: useRouteMatch('/about'),
139
- * contact: useRouteMatch('/contact')
140
- * };
141
- *
142
- * const activeRoutes = useKeyByRoute(routeMatches);
143
- * // Returns ['home'] if on home page, ['about'] if on about page, etc.
144
- *
145
- * return (
146
- * <nav>
147
- * {activeRoutes.map(route => (
148
- * <span key={route}>Active: {route}</span>
149
- * ))}
150
- * </nav>
151
- * );
152
- * }
153
- * ```
154
- */ function useKeyByRoute(routeMatches) {
155
- const keys = [];
156
- for(const key in routeMatches){
157
- const matches = routeMatches[key];
158
- if (Array.isArray(matches) ? matches.some((match)=>match !== null) : matches !== null) {
159
- keys.push(key);
160
- }
161
- }
162
- return keys;
163
- }
164
-
165
- /**
166
- * Synchronizes external state changes with a callback function, calling the callback only when state actually changes.
167
- *
168
- * @param state - The current state value to monitor for changes
169
- * @param onChange - Callback function executed when state changes
170
- * @param compare - Optional comparison function to determine if state has changed (defaults to strict equality)
171
- * @example
172
- * ```typescript
173
- * import { useSyncState } from "@leancodepl/utils";
174
- *
175
- * function MyComponent({ externalValue }: { externalValue: string }) {
176
- * useSyncState(externalValue, (newValue) => {
177
- * console.log('Value changed to:', newValue);
178
- * });
179
- *
180
- * return <div>{externalValue}</div>;
181
- * }
182
- * ```
183
- */ function useSyncState(state, onChange, compare) {
184
- const [prevState, setPrevState] = react.useState(state);
185
- const hasChanged = compare ? !compare(state, prevState) : state !== prevState;
186
- if (hasChanged) {
187
- setPrevState(state);
188
- onChange(state);
189
- }
190
- }
191
-
192
- /**
193
- * Adds a prefix to all keys in an object, creating a new object with prefixed keys.
194
- *
195
- * @template T - The type of the input object
196
- * @template TPrefix - The type of the prefix string
197
- * @param object - The object whose keys will be prefixed
198
- * @param prefix - The prefix string to add to each key
199
- * @returns A new object with all keys prefixed
200
- * @example
201
- * ```typescript
202
- * const apiData = { userId: 1, userName: 'John' };
203
- * const prefixed = addPrefix(apiData, 'api_');
204
- * // Result: { api_userId: 1, api_userName: 'John' }
205
- * ```
206
- */ function addPrefix(object, prefix) {
207
- return Object.fromEntries(Object.entries(object).map(([key, value])=>[
208
- `${prefix}${key}`,
209
- value
210
- ]));
211
- }
212
-
213
- /**
214
- * Asserts that a value is not undefined. Throws an error if the value is undefined.
215
- * This is a type assertion function that narrows the type to exclude undefined.
216
- *
217
- * @template T - The type of the value being checked
218
- * @param value - The value to check for undefined
219
- * @param message - Optional error message to use if assertion fails
220
- * @throws {Error} When the value is undefined
221
- * @example
222
- * ```typescript
223
- * function processUser(user?: User) {
224
- * assertDefined(user);
225
- * return user.name; // TypeScript knows user is defined
226
- * }
227
- * ```
228
- */ function assertDefined(value, message) {
229
- invariant(value !== undefined, message);
230
- }
231
-
232
- /**
233
- * Asserts that a value is not null or undefined. Throws an error if the value is null or undefined.
234
- * This is a type assertion function that narrows the type to exclude null and undefined.
235
- *
236
- * @template T - The type of the value being checked
237
- * @param value - The value to check for null or undefined
238
- * @param message - Optional error message to use if assertion fails
239
- * @throws {Error} When the value is null or undefined
240
- * @example
241
- * ```typescript
242
- * function processOptionalData(data?: string | null) {
243
- * assertNotEmpty(data);
244
- * return data.toUpperCase(); // TypeScript knows data is not null/undefined
245
- * }
246
- * ```
247
- */ function assertNotEmpty(value, message) {
248
- invariant(value !== null && value !== undefined, message);
249
- }
250
-
251
- /**
252
- * Asserts that a value is not null. Throws an error if the value is null.
253
- * This is a type assertion function that narrows the type to exclude null.
254
- *
255
- * @template T - The type of the value being checked
256
- * @param value - The value to check for null
257
- * @param message - Optional error message to use if assertion fails
258
- * @throws {Error} When the value is null
259
- * @example
260
- * ```typescript
261
- * function processData(data: string | null) {
262
- * assertNotNull(data);
263
- * return data.toUpperCase(); // TypeScript knows data is not null
264
- * }
265
- * ```
266
- */ function assertNotNull(value, message) {
267
- invariant(value !== null, message);
268
- }
269
-
270
- function downloadFile(dataOrUrl, options = {}) {
271
- if (typeof dataOrUrl === "string") {
272
- const { name } = options;
273
- const a = document.createElement("a");
274
- a.href = dataOrUrl;
275
- a.target = "_blank";
276
- if (name) a.download = name;
277
- a.click();
278
- } else {
279
- const url = URL.createObjectURL(dataOrUrl);
280
- downloadFile(url, options);
281
- URL.revokeObjectURL(url);
282
- }
283
- }
284
-
285
- /**
286
- * Ensures that a value is defined, returning it if defined or throwing an error if undefined.
287
- *
288
- * @template T - The type of the value being checked
289
- * @param value - The value to ensure is defined
290
- * @param message - Optional error message to use if the value is undefined
291
- * @returns The value if it is defined
292
- * @throws {Error} When the value is undefined
293
- * @example
294
- * ```typescript
295
- * function processUser(user?: User) {
296
- * const definedUser = ensureDefined(user);
297
- * return definedUser.name; // definedUser is guaranteed to be defined
298
- * }
299
- * ```
300
- */ function ensureDefined(value, message) {
301
- assertDefined(value, message);
302
- return value;
303
- }
304
-
305
- /**
306
- * Ensures that a value is not null or undefined, returning it if valid or throwing an error if empty.
307
- *
308
- * @template T - The type of the value being checked
309
- * @param value - The value to ensure is not null or undefined
310
- * @param message - Optional error message to use if the value is null or undefined
311
- * @returns The value if it is not null or undefined
312
- * @throws {Error} When the value is null or undefined
313
- * @example
314
- * ```typescript
315
- * function processOptionalData(data?: string | null) {
316
- * const validData = ensureNotEmpty(data);
317
- * return validData.toUpperCase(); // validData is guaranteed to be not null/undefined
318
- * }
319
- * ```
320
- */ function ensureNotEmpty(value, message) {
321
- assertNotEmpty(value, message);
322
- return value;
323
- }
324
-
325
- /**
326
- * Ensures that a value is not null, returning it if not null or throwing an error if null.
327
- * Unlike assertNotNull, this function returns the value for use in expressions.
328
- *
329
- * @template T - The type of the value being checked
330
- * @param value - The value to ensure is not null
331
- * @param message - Optional error message to use if the value is null
332
- * @returns The value if it is not null
333
- * @throws {Error} When the value is null
334
- * @example
335
- * ```typescript
336
- * function processData(data: string | null) {
337
- * const nonNullData = ensureNotNull(data);
338
- * return nonNullData.toUpperCase(); // nonNullData is guaranteed to be not null
339
- * }
340
- * ```
341
- */ function ensureNotNull(value, message) {
342
- assertNotNull(value, message);
343
- return value;
344
- }
345
-
346
- function _extends() {
347
- _extends = Object.assign || function assign(target) {
348
- for(var i = 1; i < arguments.length; i++){
349
- var source = arguments[i];
350
- for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
351
- }
352
- return target;
353
- };
354
- return _extends.apply(this, arguments);
355
- }
356
-
357
- function transformFirst(value, transformFn) {
358
- if (value.length === 0) {
359
- return "";
360
- }
361
- return transformFn(value[0]) + value.slice(1);
362
- }
363
- /**
364
- * Converts the first character of a string to lowercase.
365
- *
366
- * @param value - The string to transform
367
- * @returns The string with the first character in lowercase
368
- * @example
369
- * ```typescript
370
- * const result = toLowerFirst('UserName');
371
- * // Result: 'userName'
372
- * ```
373
- */ function toLowerFirst(value) {
374
- return transformFirst(value, (value)=>value.toLowerCase());
375
- }
376
- /**
377
- * Converts the first character of a string to uppercase.
378
- *
379
- * @param value - The string to transform
380
- * @returns The string with the first character in uppercase
381
- * @example
382
- * ```typescript
383
- * const result = toUpperFirst('userName');
384
- * // Result: 'UserName'
385
- * ```
386
- */ function toUpperFirst(value) {
387
- return transformFirst(value, (value)=>value.toUpperCase());
388
- }
389
-
390
- function transformDeep(value, mode) {
391
- if (value === null || value === undefined) {
392
- return undefined;
393
- }
394
- if (Array.isArray(value)) {
395
- return value.map((val)=>transformDeep(val, mode));
396
- }
397
- if (typeof value === "object") {
398
- const transformKey = mode === "capitalize" ? toUpperFirst : toLowerFirst;
399
- return Object.entries(value).reduce((accumulator, [key, value])=>_extends({}, accumulator, {
400
- [transformKey(key)]: transformDeep(value, mode)
401
- }), {});
402
- }
403
- return value;
404
- }
405
- /**
406
- * Recursively transforms all object keys to use uncapitalized (camelCase) format.
407
- *
408
- * @template T - The type of the input value
409
- * @param value - The value to transform (can be object, array, or primitive)
410
- * @returns A new object with all keys converted to camelCase
411
- * @example
412
- * ```typescript
413
- * const serverData = { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } };
414
- * const clientData = uncapitalizeDeep(serverData);
415
- * // Result: { userId: 1, userName: 'John', profile: { firstName: 'John' } }
416
- * ```
417
- */ function uncapitalizeDeep(value) {
418
- return transformDeep(value, "uncapitalize");
419
- }
420
- /**
421
- * Recursively transforms all object keys to use capitalized (PascalCase) format.
422
- *
423
- * @template T - The type of the input value
424
- * @param value - The value to transform (can be object, array, or primitive)
425
- * @returns A new object with all keys converted to PascalCase
426
- * @example
427
- * ```typescript
428
- * const clientData = { userId: 1, userName: 'John', profile: { firstName: 'John' } };
429
- * const serverData = capitalizeDeep(clientData);
430
- * // Result: { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } }
431
- * ```
432
- */ function capitalizeDeep(value) {
433
- return transformDeep(value, "capitalize");
434
- }
435
-
436
- /**
437
- * Creates a React context hook for managing optional state values with Provider and setter utilities.
438
- * Returns a hook with attached Provider component and set function for declarative value management.
439
- *
440
- * @returns Hook function with attached Provider component and set function
441
- * @example
442
- * ```typescript
443
- * import { mkValueContext } from "@leancodepl/utils";
444
- *
445
- * const useTheme = mkValueContext<string>();
446
- *
447
- * function App() {
448
- * return (
449
- * <useTheme.Provider initialValue="dark">
450
- * <ThemeConsumer />
451
- * </useTheme.Provider>
452
- * );
453
- * }
454
- *
455
- * function ThemeConsumer() {
456
- * const [theme] = useTheme();
457
- * return <div>Current theme: {theme}</div>;
458
- * }
459
- * ```
460
- * @example
461
- * ```typescript
462
- * // Using set to declaratively set context value
463
- * const useActiveUser = mkValueContext<string>();
464
- *
465
- * function UserProfile({ userId }: { userId: string }) {
466
- * useActiveUser.set(userId); // Sets value on mount, clears on unmount
467
- * return <div>Profile content</div>;
468
- * }
469
- *
470
- * function UserBadge() {
471
- * const [activeUserId] = useActiveUser();
472
- * return <div>Active user: {activeUserId}</div>;
473
- * }
474
- * ```
475
- */ function mkValueContext() {
476
- const valueContext = /*#__PURE__*/ react.createContext([
477
- undefined,
478
- ()=>void 0
479
- ]);
480
- function useValueContext() {
481
- return react.useContext(valueContext);
482
- }
483
- useValueContext.Provider = function ValueContextProvider({ children, initialValue }) {
484
- const contextValue = react.useState(initialValue);
485
- return /*#__PURE__*/ jsxRuntime.jsx(valueContext.Provider, {
486
- value: contextValue,
487
- children: children
488
- });
489
- };
490
- useValueContext.set = function useSetter(value) {
491
- const [, setValue] = useValueContext();
492
- react.useEffect(()=>void setValue(value), [
493
- setValue,
494
- value
495
- ]);
496
- react.useEffect(()=>()=>setValue(undefined), [
497
- setValue
498
- ]);
499
- };
500
- return useValueContext;
501
- }
502
-
503
- exports.addPrefix = addPrefix;
504
- exports.assertDefined = assertDefined;
505
- exports.assertNotEmpty = assertNotEmpty;
506
- exports.assertNotNull = assertNotNull;
507
- exports.capitalizeDeep = capitalizeDeep;
508
- exports.downloadFile = downloadFile;
509
- exports.ensureDefined = ensureDefined;
510
- exports.ensureNotEmpty = ensureNotEmpty;
511
- exports.ensureNotNull = ensureNotNull;
512
- exports.mkValueContext = mkValueContext;
513
- exports.toLowerFirst = toLowerFirst;
514
- exports.toUpperFirst = toUpperFirst;
515
- exports.uncapitalizeDeep = uncapitalizeDeep;
516
- exports.useBoundRunInTask = useBoundRunInTask;
517
- exports.useDialog = useDialog;
518
- exports.useKeyByRoute = useKeyByRoute;
519
- exports.useRunInTask = useRunInTask;
520
- exports.useSetUnset = useSetUnset;
521
- exports.useSyncState = useSyncState;
package/index.cjs.mjs DELETED
@@ -1,2 +0,0 @@
1
- export * from './index.cjs.js';
2
- export { _default as default } from './index.cjs.default.js';
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./src/index";