1 import { randomBytes
, randomInt
, randomUUID
} from
'node:crypto';
2 import { inspect
} from
'node:util';
4 import clone from
'just-clone';
6 import { Constants
} from
'./Constants';
7 import { type TimestampedData
, WebSocketCloseEventStatusString
} from
'../types';
9 export const logPrefix
= (prefixString
= ''): string => {
10 return `${new Date().toLocaleString()}${prefixString}`;
13 export const generateUUID
= (): string => {
17 export const validateUUID
= (uuid
: string): boolean => {
18 return /^[0-9a
-fA
-F
]{8}-[0-9a
-fA
-F
]{4}-4[0-9a
-fA
-F
]{3}-[0-9a
-fA
-F
]{4}-[0-9a
-fA
-F
]{12}$
/.test(
23 export const sleep
= async (milliSeconds
: number): Promise
<NodeJS
.Timeout
> => {
24 return new Promise((resolve
) => setTimeout(resolve
as () => void, milliSeconds
));
27 export const formatDurationMilliSeconds
= (duration
: number): string => {
28 duration
= convertToInt(duration
);
29 const hours
= Math.floor(duration
/ (3600 * 1000));
30 const minutes
= Math.floor((duration
/ 1000 - hours
* 3600) / 60);
31 const seconds
= duration
/ 1000 - hours
* 3600 - minutes
* 60;
32 let hoursStr
= hours
.toString();
33 let minutesStr
= minutes
.toString();
34 let secondsStr
= seconds
.toString();
37 hoursStr
= `0${hours.toString()}`;
40 minutesStr
= `0${minutes.toString()}`;
43 secondsStr
= `0${seconds.toString()}`;
45 return `${hoursStr}:${minutesStr}:${secondsStr.substring(0, 6)}`;
48 export const formatDurationSeconds
= (duration
: number): string => {
49 return formatDurationMilliSeconds(duration
* 1000);
52 export const convertToDate
= (
53 value
: Date | string | number | null | undefined
54 ): Date | null | undefined => {
55 if (isNullOrUndefined(value
)) {
56 return value
as null | undefined;
58 if (value
instanceof Date) {
61 if (isString(value
) || typeof value
=== 'number') {
62 return new Date(value
);
67 export const convertToInt
= (value
: unknown
): number => {
71 let changedValue
: number = value
as number;
72 if (Number.isSafeInteger(value
)) {
73 return value
as number;
75 if (typeof value
=== 'number') {
76 return Math.trunc(value
);
78 if (isString(value
)) {
79 changedValue
= parseInt(value
as string);
81 if (isNaN(changedValue
)) {
82 throw new Error(`Cannot convert to integer: ${value.toString()}`);
87 export const convertToFloat
= (value
: unknown
): number => {
91 let changedValue
: number = value
as number;
92 if (isString(value
)) {
93 changedValue
= parseFloat(value
as string);
95 if (isNaN(changedValue
)) {
96 throw new Error(`Cannot convert to float: ${value.toString()}`);
101 export const convertToBoolean
= (value
: unknown
): boolean => {
105 if (typeof value
=== 'boolean') {
107 } else if (isString(value
) && ((value
as string).toLowerCase() === 'true' || value
=== '1')) {
109 } else if (typeof value
=== 'number' && value
=== 1) {
116 export const getRandomFloat
= (max
= Number.MAX_VALUE
, min
= 0): number => {
118 throw new RangeError('Invalid interval');
120 if (max
- min
=== Infinity) {
121 throw new RangeError('Invalid interval');
123 return (randomBytes(4).readUInt32LE() / 0xffffffff) * (max
- min
) + min
;
126 export const getRandomInteger
= (max
= Constants
.MAX_RANDOM_INTEGER
, min
= 0): number => {
127 max
= Math.floor(max
);
128 if (!isNullOrUndefined(min
) && min
!== 0) {
129 min
= Math.ceil(min
);
130 return Math.floor(randomInt(min
, max
+ 1));
132 return Math.floor(randomInt(max
+ 1));
136 * Rounds the given number to the given scale.
137 * The rounding is done using the "round half away from zero" method.
139 * @param numberValue - The number to round.
140 * @param scale - The scale to round to.
141 * @returns The rounded number.
143 export const roundTo
= (numberValue
: number, scale
: number): number => {
144 const roundPower
= Math.pow(10, scale
);
145 return Math.round(numberValue
* roundPower
* (1 + Number.EPSILON
)) / roundPower
;
148 export const getRandomFloatRounded
= (max
= Number.MAX_VALUE
, min
= 0, scale
= 2): number => {
150 return roundTo(getRandomFloat(max
, min
), scale
);
152 return roundTo(getRandomFloat(max
), scale
);
155 export const getRandomFloatFluctuatedRounded
= (
157 fluctuationPercent
: number,
160 if (fluctuationPercent
< 0 || fluctuationPercent
> 100) {
161 throw new RangeError(
162 `Fluctuation percent must be between 0 and 100. Actual value: ${fluctuationPercent}`
165 if (fluctuationPercent
=== 0) {
166 return roundTo(staticValue
, scale
);
168 const fluctuationRatio
= fluctuationPercent
/ 100;
169 return getRandomFloatRounded(
170 staticValue
* (1 + fluctuationRatio
),
171 staticValue
* (1 - fluctuationRatio
),
176 export const extractTimeSeriesValues
= (timeSeries
: Array<TimestampedData
>): number[] => {
177 return timeSeries
.map((timeSeriesItem
) => timeSeriesItem
.value
);
180 export const isObject
= (item
: unknown
): boolean => {
182 isNullOrUndefined(item
) === false && typeof item
=== 'object' && Array.isArray(item
) === false
186 export const cloneObject
= <T
extends object
>(object
: T
): T
=> {
187 return clone
<T
>(object
);
190 export const hasOwnProp
= (object
: unknown
, property
: PropertyKey
): boolean => {
191 return isObject(object
) && Object.hasOwn(object
as object
, property
);
194 export const isCFEnvironment
= (): boolean => {
195 return !isNullOrUndefined(process
.env
.VCAP_APPLICATION
);
198 export const isIterable
= <T
>(obj
: T
): boolean => {
199 return !isNullOrUndefined(obj
) ? typeof obj
[Symbol
.iterator
] === 'function' : false;
202 const isString
= (value
: unknown
): boolean => {
203 return typeof value
=== 'string';
206 export const isEmptyString
= (value
: unknown
): boolean => {
207 return isNullOrUndefined(value
) || (isString(value
) && (value
as string).trim().length
=== 0);
210 export const isNotEmptyString
= (value
: unknown
): boolean => {
211 return isString(value
) && (value
as string).trim().length
> 0;
214 export const isUndefined
= (value
: unknown
): boolean => {
215 return value
=== undefined;
218 export const isNullOrUndefined
= (value
: unknown
): boolean => {
219 // eslint-disable-next-line eqeqeq, no-eq-null
220 return value
== null;
223 export const isEmptyArray
= (object
: unknown
): boolean => {
224 return Array.isArray(object
) && object
.length
=== 0;
227 export const isNotEmptyArray
= (object
: unknown
): boolean => {
228 return Array.isArray(object
) && object
.length
> 0;
231 export const isEmptyObject
= (obj
: object
): boolean => {
232 if (obj
?.constructor
!== Object) {
235 // Iterates over the keys of an object, if
236 // any exist, return false.
237 for (const _
in obj
) {
243 export const insertAt
= (str
: string, subStr
: string, pos
: number): string =>
244 `${str.slice(0, pos)}${subStr}${str.slice(pos)}`;
247 * Computes the retry delay in milliseconds using an exponential backoff algorithm.
249 * @param retryNumber - the number of retries that have already been attempted
250 * @returns delay in milliseconds
252 export const exponentialDelay
= (retryNumber
= 0, maxDelayRatio
= 0.2): number => {
253 const delay
= Math.pow(2, retryNumber
) * 100;
254 const randomSum
= delay
* maxDelayRatio
* secureRandom(); // 0-20% of the delay
255 return delay
+ randomSum
;
258 const isPromisePending
= (promise
: Promise
<unknown
>): boolean => {
259 return inspect(promise
).includes('pending');
262 export const promiseWithTimeout
= async <T
>(
266 timeoutCallback
: () => void = () => {
267 /* This is intentional */
270 // Create a timeout promise that rejects in timeout milliseconds
271 const timeoutPromise
= new Promise
<never>((_
, reject
) => {
273 if (isPromisePending(promise
)) {
275 // FIXME: The original promise shall be canceled
277 reject(timeoutError
);
281 // Returns a race between timeout promise and the passed promise
282 return Promise
.race
<T
>([promise
, timeoutPromise
]);
286 * Generates a cryptographically secure random number in the [0,1[ range
290 export const secureRandom
= (): number => {
291 return randomBytes(4).readUInt32LE() / 0x100000000;
294 export const JSONStringifyWithMapSupport
= (
295 obj
: Record
<string, unknown
> | Record
<string, unknown
>[] | Map
<unknown
, unknown
>,
298 return JSON
.stringify(
300 (key
, value
: Record
<string, unknown
>) => {
301 if (value
instanceof Map
) {
314 * Converts websocket error code to human readable string message
316 * @param code - websocket error code
317 * @returns human readable string message
319 export const getWebSocketCloseEventStatusString
= (code
: number): string => {
320 if (code
>= 0 && code
<= 999) {
322 } else if (code
>= 1016) {
324 return '(For WebSocket standard)';
325 } else if (code
<= 2999) {
326 return '(For WebSocket extensions)';
327 } else if (code
<= 3999) {
328 return '(For libraries and frameworks)';
329 } else if (code
<= 4999) {
330 return '(For applications)';
333 if (!isUndefined(WebSocketCloseEventStatusString
[code
])) {
334 return WebSocketCloseEventStatusString
[code
] as string;