@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 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
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Some useful consts. That's it.
3
+ */
4
+ export declare const DAY_MS = 86400000;
5
+ export declare const HOUR_MS = 3600000;
6
+ export declare const MIN_MS = 60000;
7
+ export declare const SEC_MS = 1000;
package/dist/consts.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Some useful consts. That's it.
3
+ */
4
+ export const DAY_MS = 86_400_000;
5
+ export const HOUR_MS = 3_600_000;
6
+ export const MIN_MS = 60_000;
7
+ export const SEC_MS = 1000;
@@ -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;
@@ -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 = () => { };
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ export * from './arrays';
2
+ export * from './consts';
3
+ export * from './control';
4
+ export * from './errors';
5
+ export * from './formatting';
6
+ export * from './numbers';
7
+ export * from './objects';
8
+ export * from './time';
9
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from './arrays';
2
+ export * from './consts';
3
+ export * from './control';
4
+ export * from './errors';
5
+ export * from './formatting';
6
+ export * from './numbers';
7
+ export * from './objects';
8
+ export * from './time';
9
+ export * from './types';
@@ -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;
@@ -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;
@@ -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
+ }
@@ -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
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Damn, I **love** TypeScript.
3
+ */
4
+ export {};
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
+ }