@softsky/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.
- package/README.md +135 -0
- package/dist/arrays.d.ts +19 -0
- package/dist/arrays.js +60 -0
- package/dist/consts.d.ts +7 -0
- package/dist/consts.js +7 -0
- package/dist/control.d.ts +45 -0
- package/dist/control.js +144 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +78 -0
- package/dist/formatting.d.ts +19 -0
- package/dist/formatting.js +75 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/numbers.d.ts +6 -0
- package/dist/numbers.js +25 -0
- package/dist/objects.d.ts +34 -0
- package/dist/objects.js +89 -0
- package/dist/time.d.ts +7 -0
- package/dist/time.js +124 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js +4 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Sky utils
|
|
2
|
+
**JavaScript/TypeScript utilities**
|
|
3
|
+
|
|
4
|
+
Basically library, but it's too simple to be on npm.
|
|
5
|
+
|
|
6
|
+
## arrays
|
|
7
|
+
Everything array related.
|
|
8
|
+
### function randomFromArray
|
|
9
|
+
Returns random element from non-empty array
|
|
10
|
+
### function shuffleArray
|
|
11
|
+
Create new shuffled array
|
|
12
|
+
### function swap
|
|
13
|
+
Swap two elements in array
|
|
14
|
+
### function binarySearch
|
|
15
|
+
Binary search in sorted array.
|
|
16
|
+
Compare function should compare your needed value with value on index passed to it.
|
|
17
|
+
If compare returns 0 it means we found target.
|
|
18
|
+
If compare returns > 0 it means we have to cut out bigger side of array.
|
|
19
|
+
If compare returns < 0 it means we have to cut out smaller side of array.
|
|
20
|
+
### function chunk
|
|
21
|
+
Split array into sub arrays of spicified size
|
|
22
|
+
|
|
23
|
+
## consts
|
|
24
|
+
Some useful consts. That's it.
|
|
25
|
+
|
|
26
|
+
## control
|
|
27
|
+
Utils related to code execution flow.
|
|
28
|
+
### const UUID
|
|
29
|
+
Get unique id
|
|
30
|
+
### async function retry
|
|
31
|
+
Retry async function
|
|
32
|
+
### function createDebouncedFunction
|
|
33
|
+
Create debounced function. Basically adds cooldown to function. Warning: throws!
|
|
34
|
+
### function createThrottledFunction
|
|
35
|
+
Create throttled function. Basically limits function calls in time period. Warning: throws!
|
|
36
|
+
### function createDelayedFunction
|
|
37
|
+
Create debounced function. Basically create function that will be called with delay,
|
|
38
|
+
but if another call comes in, we reset the timer.
|
|
39
|
+
### class ImmediatePromise
|
|
40
|
+
Promise that accepts no callback, but exposes `resolve` and `reject` methods
|
|
41
|
+
### const wait
|
|
42
|
+
setTimeout promisify
|
|
43
|
+
### const noop
|
|
44
|
+
Empty function that does nothing
|
|
45
|
+
|
|
46
|
+
## errors
|
|
47
|
+
Custom errors, finding errors and error handling.
|
|
48
|
+
### class ValidationError
|
|
49
|
+
Use as intended error. Basically 4** errors in HTTP
|
|
50
|
+
### function findErrorText
|
|
51
|
+
Find error inside anything recursively.
|
|
52
|
+
Good for finding human-readable errors.
|
|
53
|
+
Tries priority keys first.
|
|
54
|
+
Parses JSON automatically.
|
|
55
|
+
Returns undefind if nothing found.
|
|
56
|
+
|
|
57
|
+
## formatting
|
|
58
|
+
Anything related to formatting and logging.
|
|
59
|
+
### function formatTime
|
|
60
|
+
Milliseconds to human readable time. Minimum accuracy, if set to 1000 will stop at seconds
|
|
61
|
+
### const camelToSnakeCase
|
|
62
|
+
thisCase to this_case
|
|
63
|
+
### const snakeToCamelCase
|
|
64
|
+
this_case to thisCase
|
|
65
|
+
### function formatBytes
|
|
66
|
+
Bytes to KB,MB,GB,TB
|
|
67
|
+
### function log
|
|
68
|
+
Format logging
|
|
69
|
+
### class ProgressLoggerTransform
|
|
70
|
+
Can pass streams through to log a progress
|
|
71
|
+
|
|
72
|
+
## numbers
|
|
73
|
+
Numbers, math, etc.
|
|
74
|
+
### function random
|
|
75
|
+
Random number between min and max. May enable float
|
|
76
|
+
### function parseInt
|
|
77
|
+
Same as parseInt but throws
|
|
78
|
+
### function parseFloat
|
|
79
|
+
Same as parseFloat but throws
|
|
80
|
+
|
|
81
|
+
## objects
|
|
82
|
+
[object Object]
|
|
83
|
+
### function getPropertyNames
|
|
84
|
+
Get all prorerty names, including in prototype
|
|
85
|
+
### const objectMap
|
|
86
|
+
Map function like for arrays, but for objects
|
|
87
|
+
### const objectFilter
|
|
88
|
+
Filter function like for arrays, but for objects
|
|
89
|
+
### function addPrefixToObject
|
|
90
|
+
Adds prefix to every key in object
|
|
91
|
+
### function deepEquals
|
|
92
|
+
Check if objects are deep equal
|
|
93
|
+
|
|
94
|
+
**Supports:**
|
|
95
|
+
- All primitives (String, Number, BigNumber, Null, undefined, Symbol)
|
|
96
|
+
- Objects
|
|
97
|
+
- Iterables (Arrays, Map, Sets, Queries, etc.)
|
|
98
|
+
- Dates
|
|
99
|
+
|
|
100
|
+
**Not supported:**
|
|
101
|
+
- Functions
|
|
102
|
+
- Async iterables
|
|
103
|
+
- Promises
|
|
104
|
+
- etc
|
|
105
|
+
|
|
106
|
+
Behavior with object above are not defined, but
|
|
107
|
+
it will still check them by reference.
|
|
108
|
+
|
|
109
|
+
## time
|
|
110
|
+
Timers, CRON, etc.
|
|
111
|
+
### function cronInterval
|
|
112
|
+
Like setInterval but with cron. Returns clear function.
|
|
113
|
+
### function getNextCron
|
|
114
|
+
Find next cron tick after passed date
|
|
115
|
+
|
|
116
|
+
## types
|
|
117
|
+
Damn, I **love** TypeScript.
|
|
118
|
+
### type Optional
|
|
119
|
+
Make keys in object optional
|
|
120
|
+
### type AwaitedObject
|
|
121
|
+
Recursively resolves promises in objects and arrays
|
|
122
|
+
### type JSONSerializable
|
|
123
|
+
Anything that can be serialized to JSON
|
|
124
|
+
### type ObjectAddPrefix
|
|
125
|
+
Adds prefix to all keys in object
|
|
126
|
+
### type CamelToSnakeCase
|
|
127
|
+
Convert type of thisCase to this_case
|
|
128
|
+
### type ObjectCamelToSnakeCase
|
|
129
|
+
Convert object keys of thisCase to this_case
|
|
130
|
+
### type SnakeToCamel
|
|
131
|
+
Convert type of this-case to thisCase
|
|
132
|
+
### type ObjectSnakeToCamel
|
|
133
|
+
Convert object keys of this-case to thisCase
|
|
134
|
+
### type Concat
|
|
135
|
+
Concat types of array or objects
|
package/dist/arrays.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything array related.
|
|
3
|
+
*/
|
|
4
|
+
/** Returns random element from non-empty array */
|
|
5
|
+
export declare function randomFromArray<T>(array: T[]): T;
|
|
6
|
+
/** Create new shuffled array */
|
|
7
|
+
export declare function shuffleArray<T>(array_: T[]): T[];
|
|
8
|
+
/** Swap two elements in array */
|
|
9
|
+
export declare function swap<T>(array: T[], index: number, index2: number): T[];
|
|
10
|
+
/**
|
|
11
|
+
* Binary search in sorted array.
|
|
12
|
+
* Compare function should compare your needed value with value on index passed to it.
|
|
13
|
+
* If compare returns 0 it means we found target.
|
|
14
|
+
* If compare returns > 0 it means we have to cut out bigger side of array.
|
|
15
|
+
* If compare returns < 0 it means we have to cut out smaller side of array.
|
|
16
|
+
*/
|
|
17
|
+
export declare function binarySearch(size: number, compare: (index: number) => number): number;
|
|
18
|
+
/** Split array into sub arrays of spicified size */
|
|
19
|
+
export declare function chunk<T>(array: T[], chunkSize: number): T[][];
|
package/dist/arrays.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything array related.
|
|
3
|
+
*/
|
|
4
|
+
/** Returns random element from non-empty array */
|
|
5
|
+
export function randomFromArray(array) {
|
|
6
|
+
if (array.length === 0)
|
|
7
|
+
throw new Error('Can not return random element from empty array');
|
|
8
|
+
return array[Math.trunc(Math.random() * array.length)];
|
|
9
|
+
}
|
|
10
|
+
/** Create new shuffled array */
|
|
11
|
+
export function shuffleArray(array_) {
|
|
12
|
+
const array = [...array_];
|
|
13
|
+
for (let index = 0; index < array.length; index++) {
|
|
14
|
+
const index2 = Math.trunc(Math.random() * array.length);
|
|
15
|
+
const buf = array[index2];
|
|
16
|
+
array[index2] = array[index];
|
|
17
|
+
array[index] = buf;
|
|
18
|
+
}
|
|
19
|
+
return array;
|
|
20
|
+
}
|
|
21
|
+
/** Swap two elements in array */
|
|
22
|
+
export function swap(array, index, index2) {
|
|
23
|
+
const temporary = array[index2];
|
|
24
|
+
array[index2] = array[index];
|
|
25
|
+
array[index] = temporary;
|
|
26
|
+
return array;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Binary search in sorted array.
|
|
30
|
+
* Compare function should compare your needed value with value on index passed to it.
|
|
31
|
+
* If compare returns 0 it means we found target.
|
|
32
|
+
* If compare returns > 0 it means we have to cut out bigger side of array.
|
|
33
|
+
* If compare returns < 0 it means we have to cut out smaller side of array.
|
|
34
|
+
*/
|
|
35
|
+
export function binarySearch(size, compare) {
|
|
36
|
+
let low = 0;
|
|
37
|
+
let high = size - 1;
|
|
38
|
+
let position = -1;
|
|
39
|
+
while (low <= high) {
|
|
40
|
+
const mid = Math.trunc((low + high) / 2);
|
|
41
|
+
const compared = compare(mid);
|
|
42
|
+
if (compared === 0) {
|
|
43
|
+
position = mid;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
else if (compared > 0)
|
|
47
|
+
high = mid - 1;
|
|
48
|
+
else
|
|
49
|
+
low = mid + 1;
|
|
50
|
+
}
|
|
51
|
+
return position;
|
|
52
|
+
}
|
|
53
|
+
/** Split array into sub arrays of spicified size */
|
|
54
|
+
export function chunk(array, chunkSize) {
|
|
55
|
+
const copy = [...array];
|
|
56
|
+
const result = [];
|
|
57
|
+
while (copy.length > 0)
|
|
58
|
+
result.push(copy.splice(0, chunkSize));
|
|
59
|
+
return result;
|
|
60
|
+
}
|
package/dist/consts.d.ts
ADDED
package/dist/consts.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utils related to code execution flow.
|
|
3
|
+
*/
|
|
4
|
+
import { AwaitedObject, JSONSerializable } from './types';
|
|
5
|
+
/** Get unique id */
|
|
6
|
+
export declare const UUID: () => number;
|
|
7
|
+
/**
|
|
8
|
+
* Creates cached function. All arguments/results are cached.
|
|
9
|
+
* Returns [
|
|
10
|
+
* fn [cached function],
|
|
11
|
+
* delete [delete cached result for arguments]
|
|
12
|
+
* hash
|
|
13
|
+
* ]
|
|
14
|
+
*/
|
|
15
|
+
export declare function createCashedFunction<T, V extends JSONSerializable[]>(function_: (...arguments_: V) => T): readonly [(...arguments_: V) => T, (...arguments_: V) => boolean, Map<string, T>];
|
|
16
|
+
/**
|
|
17
|
+
* Creates cached function. All arguments/results are cached. Will store in cache resolved data.
|
|
18
|
+
* Returns [
|
|
19
|
+
* fn [cached function],
|
|
20
|
+
* delete [delete cached result for arguments]
|
|
21
|
+
* hash
|
|
22
|
+
* ]
|
|
23
|
+
*/
|
|
24
|
+
export declare function createCashedAsyncFunction<T, V extends JSONSerializable[]>(function_: (...arguments_: V) => Promise<T>): readonly [(...arguments_: V) => Promise<T>, (...arguments_: V) => boolean, Map<string, T>];
|
|
25
|
+
/** Retry async function */
|
|
26
|
+
export declare function retry<T>(function_: () => Promise<T>, retries?: number, interval?: number | number[]): Promise<T>;
|
|
27
|
+
/** Create debounced function. Basically adds cooldown to function. Warning: throws! */
|
|
28
|
+
export declare function createDebouncedFunction<T, V extends unknown[]>(function_: (...arguments_: V) => T, time: number): (...arguments_: V) => T;
|
|
29
|
+
/** Create throttled function. Basically limits function calls in time period. Warning: throws! */
|
|
30
|
+
export declare function createThrottledFunction<T, V extends unknown[]>(function_: (...arguments_: V) => T, calls: number, time: number): (...arguments_: V) => T;
|
|
31
|
+
/** Create debounced function. Basically create function that will be called with delay,
|
|
32
|
+
* but if another call comes in, we reset the timer. */
|
|
33
|
+
export declare function createDelayedFunction<T, V extends unknown[]>(function_: (...arguments_: V) => T, time: number): (...arguments_: V) => Promise<T>;
|
|
34
|
+
/** Promise that accepts no callback, but exposes `resolve` and `reject` methods */
|
|
35
|
+
export declare class ImmediatePromise<T> extends Promise<T> {
|
|
36
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
37
|
+
reject: (reason?: unknown) => void;
|
|
38
|
+
constructor();
|
|
39
|
+
}
|
|
40
|
+
/** Recursively resolves promises in objects and arrays */
|
|
41
|
+
export default function deepPromiseAll<T>(input: T): Promise<AwaitedObject<T>>;
|
|
42
|
+
/** setTimeout promisify */
|
|
43
|
+
export declare const wait: (time: number) => Promise<unknown>;
|
|
44
|
+
/** Empty function that does nothing */
|
|
45
|
+
export declare const noop: () => void;
|
package/dist/control.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utils related to code execution flow.
|
|
3
|
+
*/
|
|
4
|
+
let _uuid = Date.now() * 1000;
|
|
5
|
+
/** Get unique id */
|
|
6
|
+
export const UUID = () => _uuid++;
|
|
7
|
+
/**
|
|
8
|
+
* Creates cached function. All arguments/results are cached.
|
|
9
|
+
* Returns [
|
|
10
|
+
* fn [cached function],
|
|
11
|
+
* delete [delete cached result for arguments]
|
|
12
|
+
* hash
|
|
13
|
+
* ]
|
|
14
|
+
*/
|
|
15
|
+
export function createCashedFunction(function_) {
|
|
16
|
+
const hash = new Map();
|
|
17
|
+
return [
|
|
18
|
+
(...arguments_) => {
|
|
19
|
+
const key = JSON.stringify(arguments_);
|
|
20
|
+
const value = hash.get(key);
|
|
21
|
+
if (value)
|
|
22
|
+
return value;
|
|
23
|
+
const newValue = function_(...arguments_);
|
|
24
|
+
hash.set(key, newValue);
|
|
25
|
+
return newValue;
|
|
26
|
+
},
|
|
27
|
+
(...arguments_) => hash.delete(JSON.stringify(arguments_)),
|
|
28
|
+
hash,
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates cached function. All arguments/results are cached. Will store in cache resolved data.
|
|
33
|
+
* Returns [
|
|
34
|
+
* fn [cached function],
|
|
35
|
+
* delete [delete cached result for arguments]
|
|
36
|
+
* hash
|
|
37
|
+
* ]
|
|
38
|
+
*/
|
|
39
|
+
export function createCashedAsyncFunction(function_) {
|
|
40
|
+
const hash = new Map();
|
|
41
|
+
return [
|
|
42
|
+
async (...arguments_) => {
|
|
43
|
+
const key = JSON.stringify(arguments_);
|
|
44
|
+
const value = hash.get(key);
|
|
45
|
+
if (value)
|
|
46
|
+
return value;
|
|
47
|
+
const newValue = await function_(...arguments_);
|
|
48
|
+
hash.set(key, newValue);
|
|
49
|
+
return newValue;
|
|
50
|
+
},
|
|
51
|
+
(...arguments_) => hash.delete(JSON.stringify(arguments_)),
|
|
52
|
+
hash,
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
/** Retry async function */
|
|
56
|
+
export async function retry(function_, retries = 5, interval = 0) {
|
|
57
|
+
try {
|
|
58
|
+
return await function_();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (retries === 0)
|
|
62
|
+
throw error;
|
|
63
|
+
await wait(typeof interval === 'number'
|
|
64
|
+
? interval
|
|
65
|
+
: interval[interval.length - retries]);
|
|
66
|
+
return retry(function_, retries - 1, interval);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Create debounced function. Basically adds cooldown to function. Warning: throws! */
|
|
70
|
+
export function createDebouncedFunction(function_, time) {
|
|
71
|
+
let nextExec = 0;
|
|
72
|
+
return (...arguments_) => {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (nextExec > now)
|
|
75
|
+
throw new Error('Debounced');
|
|
76
|
+
nextExec = now + time;
|
|
77
|
+
return function_(...arguments_);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Create throttled function. Basically limits function calls in time period. Warning: throws! */
|
|
81
|
+
export function createThrottledFunction(function_, calls, time) {
|
|
82
|
+
let nextClear = 0;
|
|
83
|
+
let amount = 0;
|
|
84
|
+
return (...arguments_) => {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
if (nextClear <= now) {
|
|
87
|
+
nextClear = now + time;
|
|
88
|
+
amount = 0;
|
|
89
|
+
}
|
|
90
|
+
if (amount === calls)
|
|
91
|
+
throw new Error('Throttled');
|
|
92
|
+
amount++;
|
|
93
|
+
return function_(...arguments_);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Create debounced function. Basically create function that will be called with delay,
|
|
97
|
+
* but if another call comes in, we reset the timer. */
|
|
98
|
+
export function createDelayedFunction(function_, time) {
|
|
99
|
+
let timeout;
|
|
100
|
+
let activePromise;
|
|
101
|
+
return (...arguments_) => {
|
|
102
|
+
activePromise ??= new ImmediatePromise();
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
timeout = setTimeout(() => {
|
|
105
|
+
activePromise?.resolve(function_(...arguments_));
|
|
106
|
+
activePromise = undefined;
|
|
107
|
+
}, time);
|
|
108
|
+
return activePromise;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Promise that accepts no callback, but exposes `resolve` and `reject` methods */
|
|
112
|
+
export class ImmediatePromise extends Promise {
|
|
113
|
+
resolve;
|
|
114
|
+
reject;
|
|
115
|
+
constructor() {
|
|
116
|
+
let resolve = noop;
|
|
117
|
+
let reject = noop;
|
|
118
|
+
super((r, index) => {
|
|
119
|
+
resolve = r;
|
|
120
|
+
reject = index;
|
|
121
|
+
});
|
|
122
|
+
this.resolve = resolve;
|
|
123
|
+
this.reject = reject;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Recursively resolves promises in objects and arrays */
|
|
127
|
+
export default async function deepPromiseAll(input) {
|
|
128
|
+
if (input instanceof Promise)
|
|
129
|
+
return deepPromiseAll(await input);
|
|
130
|
+
if (Array.isArray(input))
|
|
131
|
+
return Promise.all(input.map(item => deepPromiseAll(item)));
|
|
132
|
+
if (typeof input === 'object' && input !== null) {
|
|
133
|
+
return Object.fromEntries(await Promise.all(Object.entries(input).map(async ([key, value]) => [
|
|
134
|
+
key,
|
|
135
|
+
await deepPromiseAll(value),
|
|
136
|
+
])));
|
|
137
|
+
}
|
|
138
|
+
return input;
|
|
139
|
+
}
|
|
140
|
+
/** setTimeout promisify */
|
|
141
|
+
export const wait = (time) => new Promise(r => setTimeout(r, time));
|
|
142
|
+
/** Empty function that does nothing */
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
144
|
+
export const noop = () => { };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom errors, finding errors and error handling.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Use as intended error. Basically 4** errors in HTTP
|
|
6
|
+
*/
|
|
7
|
+
export declare class ValidationError extends Error {
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Find error inside anything recursively.
|
|
12
|
+
* Good for finding human-readable errors.
|
|
13
|
+
* Tries priority keys first.
|
|
14
|
+
* Parses JSON automatically.
|
|
15
|
+
* Returns undefind if nothing found.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findErrorText(error: unknown, priorityErrorKeys?: string[], stack?: Set<unknown>): string | undefined;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom errors, finding errors and error handling.
|
|
3
|
+
*/
|
|
4
|
+
import { getPropertyNames } from './objects';
|
|
5
|
+
/**
|
|
6
|
+
* Use as intended error. Basically 4** errors in HTTP
|
|
7
|
+
*/
|
|
8
|
+
export class ValidationError extends Error {
|
|
9
|
+
name = 'ValidationError';
|
|
10
|
+
}
|
|
11
|
+
const defaultPriorityErrorKeys = [
|
|
12
|
+
'message',
|
|
13
|
+
'messages',
|
|
14
|
+
'msg',
|
|
15
|
+
'msgs',
|
|
16
|
+
'text',
|
|
17
|
+
'txt',
|
|
18
|
+
'error',
|
|
19
|
+
'errors',
|
|
20
|
+
'err',
|
|
21
|
+
'e',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Find error inside anything recursively.
|
|
25
|
+
* Good for finding human-readable errors.
|
|
26
|
+
* Tries priority keys first.
|
|
27
|
+
* Parses JSON automatically.
|
|
28
|
+
* Returns undefind if nothing found.
|
|
29
|
+
*/
|
|
30
|
+
export function findErrorText(error, priorityErrorKeys = defaultPriorityErrorKeys, stack = new Set()) {
|
|
31
|
+
if (!error || stack.has(error))
|
|
32
|
+
return;
|
|
33
|
+
stack.add(error);
|
|
34
|
+
if (stack.size > 1000)
|
|
35
|
+
throw new Error('Stack overflow');
|
|
36
|
+
if (typeof error === 'string')
|
|
37
|
+
return findErrorTextInString(error, priorityErrorKeys, stack);
|
|
38
|
+
if (typeof error === 'object') {
|
|
39
|
+
if (Symbol.iterator in error && !Array.isArray(error))
|
|
40
|
+
return findErrorTextInObject({ obj: error, iteratorResult: [...error] }, priorityErrorKeys, stack);
|
|
41
|
+
return findErrorTextInObject(error, priorityErrorKeys, stack);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Find string that looks like a human-readable error using some simple heuristics */
|
|
45
|
+
function findErrorTextInString(error, priorityErrorKeys = defaultPriorityErrorKeys, stack = new Set()) {
|
|
46
|
+
try {
|
|
47
|
+
return findErrorText(JSON.parse(error), priorityErrorKeys, stack);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
if (error.length < 4 || error === '[object Object]')
|
|
51
|
+
return;
|
|
52
|
+
return error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function findErrorTextInObject(error, priorityErrorKeys = defaultPriorityErrorKeys, stack = new Set()) {
|
|
56
|
+
const keys = [...getPropertyNames(error)]
|
|
57
|
+
.map((key) => {
|
|
58
|
+
let score = priorityErrorKeys.indexOf(key);
|
|
59
|
+
if (score === -1) {
|
|
60
|
+
if (!Number.isNaN(key))
|
|
61
|
+
score = priorityErrorKeys.length;
|
|
62
|
+
else if (typeof error[key] === 'function') {
|
|
63
|
+
score = Number.MAX_SAFE_INTEGER - 1;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
score = Number.MAX_SAFE_INTEGER;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [key, score];
|
|
70
|
+
})
|
|
71
|
+
.sort(([, v], [, v2]) => v - v2)
|
|
72
|
+
.map(([k]) => k);
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
const result = findErrorText(error[key], priorityErrorKeys, stack);
|
|
75
|
+
if (result)
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anything related to formatting and logging.
|
|
3
|
+
*/
|
|
4
|
+
/** Milliseconds to human readable time. Minimum accuracy, if set to 1000 will stop at seconds */
|
|
5
|
+
export declare function formatTime(time: number, min?: number, ranges?: [number, string][]): string;
|
|
6
|
+
/** thisCase to this_case */
|
|
7
|
+
export declare const camelToSnakeCase: (string_: string) => string;
|
|
8
|
+
/** this_case to thisCase */
|
|
9
|
+
export declare const snakeToCamelCase: (string_: string) => string;
|
|
10
|
+
/** Bytes to KB,MB,GB,TB */
|
|
11
|
+
export declare function formatBytes(bytes: number): string;
|
|
12
|
+
/** Format logging */
|
|
13
|
+
export declare function log(...agrs: unknown[]): void;
|
|
14
|
+
/** Can pass streams through to log a progress */
|
|
15
|
+
export declare class ProgressLoggerTransform<T extends {
|
|
16
|
+
length: number;
|
|
17
|
+
}> extends TransformStream<T> {
|
|
18
|
+
constructor(string_: string, logInterval: number, maxSize?: number);
|
|
19
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anything related to formatting and logging.
|
|
3
|
+
*/
|
|
4
|
+
/** Milliseconds to human readable time. Minimum accuracy, if set to 1000 will stop at seconds */
|
|
5
|
+
export function formatTime(time, min = 0, ranges = [
|
|
6
|
+
[31_536_000_000, 'y'],
|
|
7
|
+
[86_400_000, 'd'],
|
|
8
|
+
[3_600_000, 'h'],
|
|
9
|
+
[60_000, 'm'],
|
|
10
|
+
[1000, 's'],
|
|
11
|
+
[1, 'ms'],
|
|
12
|
+
]) {
|
|
13
|
+
let output = '';
|
|
14
|
+
for (const [ms, title] of ranges) {
|
|
15
|
+
if (min && time < min)
|
|
16
|
+
break;
|
|
17
|
+
if (time < ms)
|
|
18
|
+
continue;
|
|
19
|
+
const value = Math.trunc(time / ms);
|
|
20
|
+
if (value !== 0)
|
|
21
|
+
output += ` ${value}${title}`;
|
|
22
|
+
time %= ms;
|
|
23
|
+
}
|
|
24
|
+
return output;
|
|
25
|
+
}
|
|
26
|
+
/** thisCase to this_case */
|
|
27
|
+
export const camelToSnakeCase = (string_) => string_.replaceAll(/[A-Z]+/g, letter => `_${letter.toLowerCase()}`);
|
|
28
|
+
/** this_case to thisCase */
|
|
29
|
+
export const snakeToCamelCase = (string_) => string_.replaceAll(/_[a-z]/g, letter => letter[1].toUpperCase());
|
|
30
|
+
/** Bytes to KB,MB,GB,TB */
|
|
31
|
+
export function formatBytes(bytes) {
|
|
32
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
33
|
+
if (bytes === 0)
|
|
34
|
+
return `0B`;
|
|
35
|
+
const pow = Math.trunc(Math.log(bytes) / Math.log(1024));
|
|
36
|
+
const maxPow = Math.min(pow, sizes.length - 1);
|
|
37
|
+
return `${Number.parseFloat((bytes / Math.pow(1024, maxPow)).toFixed(2))}${sizes[maxPow]}`;
|
|
38
|
+
}
|
|
39
|
+
/** Format logging */
|
|
40
|
+
export function log(...agrs) {
|
|
41
|
+
console.log(new Date().toLocaleString('ru'), ...agrs);
|
|
42
|
+
}
|
|
43
|
+
/** Can pass streams through to log a progress */
|
|
44
|
+
export class ProgressLoggerTransform extends TransformStream {
|
|
45
|
+
constructor(string_, logInterval, maxSize) {
|
|
46
|
+
let bytes = 0;
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
let lastBytes = 0;
|
|
49
|
+
super({
|
|
50
|
+
transform(chunk, controller) {
|
|
51
|
+
controller.enqueue(chunk);
|
|
52
|
+
bytes += chunk.length;
|
|
53
|
+
},
|
|
54
|
+
flush() {
|
|
55
|
+
clearInterval(interval);
|
|
56
|
+
log('Done!');
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const interval = setInterval(() => {
|
|
60
|
+
let message = string_;
|
|
61
|
+
const speed = (bytes - lastBytes) / logInterval;
|
|
62
|
+
message = message
|
|
63
|
+
.replace('%b', formatBytes(bytes))
|
|
64
|
+
.replace('%t', formatTime(Date.now() - start, 1000))
|
|
65
|
+
.replace('%s', formatBytes(speed));
|
|
66
|
+
if (maxSize)
|
|
67
|
+
message = message
|
|
68
|
+
.replace('%lt', formatTime(Math.trunc((maxSize - bytes) / speed) * 1000))
|
|
69
|
+
.replace('%p', Math.trunc((bytes / maxSize) * 100).toString())
|
|
70
|
+
.replace('%s', formatBytes(maxSize));
|
|
71
|
+
log(message);
|
|
72
|
+
lastBytes = bytes;
|
|
73
|
+
}, logInterval * 1000);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Random number between min and max. May enable float */
|
|
2
|
+
export declare function random(min: number, max: number, float?: boolean): number;
|
|
3
|
+
/** Same as parseInt but throws */
|
|
4
|
+
export declare function parseInt(parameter: unknown, radix?: number): number;
|
|
5
|
+
/** Same as parseFloat but throws */
|
|
6
|
+
export declare function parseFloat(parameter: unknown): number;
|
package/dist/numbers.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Numbers, math, etc.
|
|
3
|
+
*/
|
|
4
|
+
import { ValidationError } from './errors';
|
|
5
|
+
/** Random number between min and max. May enable float */
|
|
6
|
+
export function random(min, max, float) {
|
|
7
|
+
const number_ = Math.random() * (max - min) + min;
|
|
8
|
+
return float ? number_ : Math.round(number_);
|
|
9
|
+
}
|
|
10
|
+
/** Same as parseInt but throws */
|
|
11
|
+
export function parseInt(parameter, radix) {
|
|
12
|
+
const n = Number.parseInt(parameter, radix);
|
|
13
|
+
if (Number.isNaN(n) || !Number.isSafeInteger(n))
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
15
|
+
throw new ValidationError(`Can not parse "${parameter}" to integer`);
|
|
16
|
+
return n;
|
|
17
|
+
}
|
|
18
|
+
/** Same as parseFloat but throws */
|
|
19
|
+
export function parseFloat(parameter) {
|
|
20
|
+
const n = Number.parseFloat(parameter);
|
|
21
|
+
if (Number.isNaN(n) || !Number.isFinite(n))
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
23
|
+
throw new ValidationError(`Can not parse "${parameter}" to float`);
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [object Object]
|
|
3
|
+
*/
|
|
4
|
+
import { ObjectAddPrefix } from './types';
|
|
5
|
+
/** Get all prorerty names, including in prototype */
|
|
6
|
+
export declare function getPropertyNames(object: object, keys?: Set<unknown>): Set<unknown>;
|
|
7
|
+
/** Map function like for arrays, but for objects */
|
|
8
|
+
export declare const objectMap: (object: object, function_: (key: string, value: unknown) => [string, unknown]) => {
|
|
9
|
+
[k: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
/** Filter function like for arrays, but for objects */
|
|
12
|
+
export declare const objectFilter: (object: object, function_: (key: string, value: unknown) => unknown) => {
|
|
13
|
+
[k: string]: any;
|
|
14
|
+
};
|
|
15
|
+
/** Adds prefix to every key in object */
|
|
16
|
+
export declare function addPrefixToObject<T extends Record<string, unknown>, P extends string>(object: Record<string, T>, prefix: P): ObjectAddPrefix<T, P>;
|
|
17
|
+
/** Check if objects are deep equal
|
|
18
|
+
*
|
|
19
|
+
* **Supports:**
|
|
20
|
+
* - All primitives (String, Number, BigNumber, Null, undefined, Symbol)
|
|
21
|
+
* - Objects
|
|
22
|
+
* - Iterables (Arrays, Map, Sets, Queries, etc.)
|
|
23
|
+
* - Dates
|
|
24
|
+
*
|
|
25
|
+
* **Not supported:**
|
|
26
|
+
* - Functions
|
|
27
|
+
* - Async iterables
|
|
28
|
+
* - Promises
|
|
29
|
+
* - etc
|
|
30
|
+
*
|
|
31
|
+
* Behavior with object above are not defined, but
|
|
32
|
+
* it will still check them by reference.
|
|
33
|
+
*/
|
|
34
|
+
export declare function deepEquals(a: unknown, b: unknown, stack?: WeakSet<WeakKey>): boolean;
|
package/dist/objects.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [object Object]
|
|
3
|
+
*/
|
|
4
|
+
/** Get all prorerty names, including in prototype */
|
|
5
|
+
export function getPropertyNames(object, keys = new Set()) {
|
|
6
|
+
const own = Object.getOwnPropertyNames(object);
|
|
7
|
+
for (let index = 0; index < own.length; index++)
|
|
8
|
+
keys.add(own[index]);
|
|
9
|
+
const proto = Object.getPrototypeOf(object);
|
|
10
|
+
if (proto)
|
|
11
|
+
getPropertyNames(proto, keys);
|
|
12
|
+
return keys;
|
|
13
|
+
}
|
|
14
|
+
/** Map function like for arrays, but for objects */
|
|
15
|
+
export const objectMap = (object, function_) => Object.fromEntries(Object.entries(object).map(([key, value]) => function_(key, value)));
|
|
16
|
+
/** Filter function like for arrays, but for objects */
|
|
17
|
+
export const objectFilter = (object, function_) => Object.fromEntries(Object.entries(object).filter(([key, value]) => function_(key, value)));
|
|
18
|
+
/** Adds prefix to every key in object */
|
|
19
|
+
export function addPrefixToObject(object, prefix) {
|
|
20
|
+
const n = {};
|
|
21
|
+
for (const key in object)
|
|
22
|
+
n[prefix + key] = object[key];
|
|
23
|
+
return n;
|
|
24
|
+
}
|
|
25
|
+
/** Check if objects are deep equal
|
|
26
|
+
*
|
|
27
|
+
* **Supports:**
|
|
28
|
+
* - All primitives (String, Number, BigNumber, Null, undefined, Symbol)
|
|
29
|
+
* - Objects
|
|
30
|
+
* - Iterables (Arrays, Map, Sets, Queries, etc.)
|
|
31
|
+
* - Dates
|
|
32
|
+
*
|
|
33
|
+
* **Not supported:**
|
|
34
|
+
* - Functions
|
|
35
|
+
* - Async iterables
|
|
36
|
+
* - Promises
|
|
37
|
+
* - etc
|
|
38
|
+
*
|
|
39
|
+
* Behavior with object above are not defined, but
|
|
40
|
+
* it will still check them by reference.
|
|
41
|
+
*/
|
|
42
|
+
export function deepEquals(a, b, stack = new WeakSet()) {
|
|
43
|
+
// Primitives
|
|
44
|
+
if (a === b)
|
|
45
|
+
return true;
|
|
46
|
+
if (typeof a !== typeof b
|
|
47
|
+
|| typeof a !== 'object'
|
|
48
|
+
|| typeof b !== 'object'
|
|
49
|
+
|| a === null
|
|
50
|
+
|| b === null)
|
|
51
|
+
return false;
|
|
52
|
+
// Assume that already checked objects are equal
|
|
53
|
+
if (stack.has(a) || stack.has(b))
|
|
54
|
+
return true;
|
|
55
|
+
stack.add(a);
|
|
56
|
+
stack.add(b);
|
|
57
|
+
// Arrays
|
|
58
|
+
if (Array.isArray(a)) {
|
|
59
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
60
|
+
return false;
|
|
61
|
+
for (let index = 0; index < a.length; index++)
|
|
62
|
+
if (!deepEquals(a[index], b[index], stack))
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(b))
|
|
67
|
+
return false;
|
|
68
|
+
// Dates
|
|
69
|
+
if (a instanceof Date)
|
|
70
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
71
|
+
if (b instanceof Date)
|
|
72
|
+
return false;
|
|
73
|
+
// Iterables
|
|
74
|
+
if (Symbol.iterator in a)
|
|
75
|
+
return (Symbol.iterator in b
|
|
76
|
+
&& deepEquals([...a], [...b], stack));
|
|
77
|
+
if (Symbol.iterator in b)
|
|
78
|
+
return false;
|
|
79
|
+
// Other objects
|
|
80
|
+
const aKeys = getPropertyNames(a);
|
|
81
|
+
const bKeys = getPropertyNames(b);
|
|
82
|
+
if (aKeys.size !== bKeys.size)
|
|
83
|
+
return false;
|
|
84
|
+
for (const property of getPropertyNames(a))
|
|
85
|
+
if (!bKeys.has(property)
|
|
86
|
+
|| !deepEquals(a[property], b[property], stack))
|
|
87
|
+
return false;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
package/dist/time.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timers, CRON, etc.
|
|
3
|
+
*/
|
|
4
|
+
/** Like setInterval but with cron. Returns clear function. */
|
|
5
|
+
export declare function cronInterval(function_: () => unknown, cronString: string): () => void;
|
|
6
|
+
/** Find next cron tick after passed date */
|
|
7
|
+
export declare function getNextCron(cronString: string, datetime?: Date): Date;
|
package/dist/time.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timers, CRON, etc.
|
|
3
|
+
*/
|
|
4
|
+
import { HOUR_MS } from './consts';
|
|
5
|
+
import { ValidationError } from './errors';
|
|
6
|
+
/** Like setInterval but with cron. Returns clear function. */
|
|
7
|
+
export function cronInterval(function_, cronString) {
|
|
8
|
+
let timeout;
|
|
9
|
+
let next = getNextCron(cronString).getTime();
|
|
10
|
+
const r = () => {
|
|
11
|
+
const d = Date.now() - next;
|
|
12
|
+
if (d < 1) {
|
|
13
|
+
next = getNextCron(cronString).getTime();
|
|
14
|
+
function_();
|
|
15
|
+
}
|
|
16
|
+
timeout = setTimeout(r, Math.min(d, HOUR_MS));
|
|
17
|
+
};
|
|
18
|
+
r();
|
|
19
|
+
return () => {
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Find next cron tick after passed date */
|
|
24
|
+
export function getNextCron(cronString, datetime = new Date()) {
|
|
25
|
+
const cron = cronString.split(' ');
|
|
26
|
+
if (cron.length !== 5)
|
|
27
|
+
throw new ValidationError('Only 5 cron params supported');
|
|
28
|
+
const dt = new Date(datetime);
|
|
29
|
+
dt.setSeconds(0, 0);
|
|
30
|
+
const items = [
|
|
31
|
+
[
|
|
32
|
+
parseCronItem(cron[4], 0, 6),
|
|
33
|
+
dt.getDay.bind(dt),
|
|
34
|
+
(x) => dt.setDate(dt.getDate()
|
|
35
|
+
+ (dt.getDay() < x ? x - dt.getDay() : 7 - dt.getDay() + x)),
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
parseCronItem(cron[3], 1, 12),
|
|
39
|
+
() => dt.getMonth() + 1,
|
|
40
|
+
(x) => dt.setMonth(x - 1),
|
|
41
|
+
],
|
|
42
|
+
[parseCronItem(cron[2], 1, 31), dt.getDate.bind(dt), dt.setDate.bind(dt)],
|
|
43
|
+
[
|
|
44
|
+
parseCronItem(cron[1], 0, 23),
|
|
45
|
+
dt.getHours.bind(dt),
|
|
46
|
+
dt.setHours.bind(dt),
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
parseCronItem(cron[0], 0, 59),
|
|
50
|
+
dt.getMinutes.bind(dt),
|
|
51
|
+
dt.setMinutes.bind(dt),
|
|
52
|
+
],
|
|
53
|
+
];
|
|
54
|
+
function r() {
|
|
55
|
+
for (let index = 0; index < items.length; index++) {
|
|
56
|
+
const [ok, getN, setN] = items[index];
|
|
57
|
+
const n = getN();
|
|
58
|
+
// If OK continue
|
|
59
|
+
if (ok.includes(n))
|
|
60
|
+
continue;
|
|
61
|
+
// If not ok, change every possible lower value lowest ok
|
|
62
|
+
for (let index2 = index === 0 ? 3 : index + 1; index2 < items.length; index2++) {
|
|
63
|
+
const [ok, , setN] = items[index2];
|
|
64
|
+
setN(ok[0]);
|
|
65
|
+
}
|
|
66
|
+
const found = ok.find(x => x > n);
|
|
67
|
+
if (found)
|
|
68
|
+
setN(found);
|
|
69
|
+
else {
|
|
70
|
+
// Set lowest value, increase item before and recheck everything
|
|
71
|
+
setN(ok[0]);
|
|
72
|
+
if (index > 1) {
|
|
73
|
+
const [, getN, setN] = items[index - 1];
|
|
74
|
+
setN(getN() + 1);
|
|
75
|
+
r();
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
r();
|
|
82
|
+
return dt;
|
|
83
|
+
}
|
|
84
|
+
function parseCronItem(cronString, min, max) {
|
|
85
|
+
const cron = cronString.split(',');
|
|
86
|
+
const ok = new Set();
|
|
87
|
+
const error = new ValidationError(`Can't parse CRON string: ${cronString}`);
|
|
88
|
+
for (const item of cron) {
|
|
89
|
+
// If everything add every possible value and skip others
|
|
90
|
+
if (item === '*') {
|
|
91
|
+
for (let index = min; index <= max; index++)
|
|
92
|
+
ok.add(index);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
// If range
|
|
96
|
+
let split = item.split('-');
|
|
97
|
+
if (split.length === 2) {
|
|
98
|
+
const a = Number.parseInt(split[0]);
|
|
99
|
+
const b = Number.parseInt(split[1]);
|
|
100
|
+
if (Number.isNaN(a) || Number.isNaN(b) || a < min || a > b || b > max)
|
|
101
|
+
throw error;
|
|
102
|
+
for (let index = a; index <= b; index++)
|
|
103
|
+
ok.add(index);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// If stepped
|
|
107
|
+
split = item.split('/');
|
|
108
|
+
if (split.length === 2) {
|
|
109
|
+
const step = Number.parseInt(split[1]);
|
|
110
|
+
if (Number.isNaN(step))
|
|
111
|
+
throw error;
|
|
112
|
+
const items = parseCronItem(split[0], min, max);
|
|
113
|
+
for (let index = 0; index < items.length; index += step)
|
|
114
|
+
ok.add(items[index]);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// If everything else failed check for simple number
|
|
118
|
+
const n = Number.parseInt(item);
|
|
119
|
+
if (Number.isNaN(n) || n < min || n > max)
|
|
120
|
+
throw error;
|
|
121
|
+
ok.add(n);
|
|
122
|
+
}
|
|
123
|
+
return [...ok].sort((a, b) => a - b);
|
|
124
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Damn, I **love** TypeScript.
|
|
3
|
+
*/
|
|
4
|
+
/** Make keys in object optional */
|
|
5
|
+
export type Optional<T, K extends keyof any> = Omit<T, K & keyof T> & Partial<Pick<T, K & keyof T>>;
|
|
6
|
+
/** Recursively resolves promises in objects and arrays */
|
|
7
|
+
export type AwaitedObject<T> = {
|
|
8
|
+
[K in keyof T]: T[K] extends Promise<infer U> ? U : T[K] extends object ? AwaitedObject<T[K]> : T[K];
|
|
9
|
+
};
|
|
10
|
+
/** Anything that can be serialized to JSON */
|
|
11
|
+
export type JSONSerializable = string | number | boolean | null | undefined | {
|
|
12
|
+
[key: string]: JSONSerializable;
|
|
13
|
+
} | JSONSerializable[];
|
|
14
|
+
/** Adds prefix to all keys in object */
|
|
15
|
+
export type ObjectAddPrefix<T, P extends string> = {
|
|
16
|
+
[K in keyof T as `${P}${string & K}`]: T[K];
|
|
17
|
+
};
|
|
18
|
+
/** Convert type of thisCase to this_case */
|
|
19
|
+
export type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}` ? U extends Uncapitalize<U> ? `${Lowercase<T>}${CamelToSnakeCase<U>}` : `${Lowercase<T>}_${CamelToSnakeCase<Uncapitalize<U>>}` : S;
|
|
20
|
+
/** Convert object keys of thisCase to this_case */
|
|
21
|
+
export type ObjectCamelToSnakeCase<T> = {
|
|
22
|
+
[K in keyof T as CamelToSnakeCase<string & K>]: T[K];
|
|
23
|
+
};
|
|
24
|
+
/** Convert type of this-case to thisCase */
|
|
25
|
+
export type SnakeToCamel<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<SnakeToCamel<U>>}` : S;
|
|
26
|
+
/** Convert object keys of this-case to thisCase */
|
|
27
|
+
export type ObjectSnakeToCamel<T> = {
|
|
28
|
+
[K in keyof T as SnakeToCamel<string & K>]: T[K];
|
|
29
|
+
};
|
|
30
|
+
/** Concat types of array or objects */
|
|
31
|
+
export type Concat<T, U> = T extends any[] ? U extends any[] ? [...T, ...U] : never : T & U;
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@softsky/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JavaScript/TypeScript utilities",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "eslint \"./src/**/*.ts\" --fix && tsc",
|
|
8
|
+
"gen-readme": "bun ./generate-readme.ts"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/SoundOfTheSky/utils.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "SoundOfTheSky",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/SoundOfTheSky/utils/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/SoundOfTheSky/utils#readme",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@softsky/configs": "^1.0.4",
|
|
22
|
+
"@types/bun": "^1.1.12"
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist/**/*"]
|
|
25
|
+
}
|