export const once = <T extends (...args: any[]) => any>(fn: T): T => {
let hasBeenCalled = false
let result: ReturnType<T>
+ let thrownError: Error | undefined
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (this: any, ...args: Parameters<T>): ReturnType<T> {
if (!hasBeenCalled) {
hasBeenCalled = true
- result = fn.apply(this, args) as ReturnType<T>
+ try {
+ result = fn.apply(this, args) as ReturnType<T>
+ } catch (err) {
+ thrownError = err as Error
+ }
+ }
+ if (thrownError != null) {
+ throw thrownError
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return result
export const validateUUID = (
uuid: `${string}-${string}-${string}-${string}-${string}`
): uuid is `${string}-${string}-${string}-${string}-${string}` => {
- 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(uuid)
+ return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test(
+ uuid
+ )
}
export const sleep = async (milliSeconds: number): Promise<NodeJS.Timeout> => {
- return await new Promise<NodeJS.Timeout>(resolve =>
- setTimeout(resolve as () => void, milliSeconds)
- )
+ return await new Promise<NodeJS.Timeout>(resolve => {
+ const timeout = setTimeout(() => {
+ resolve(timeout)
+ }, milliSeconds)
+ })
}
export const formatDurationMilliSeconds = (duration: number): string => {
// More efficient time validation function than the one provided by date-fns
export const isValidDate = (date: Date | number | undefined): date is Date | number => {
if (typeof date === 'number') {
- return !Number.isNaN(date)
+ return Number.isFinite(date)
} else if (isDate(date)) {
return !Number.isNaN(date.getTime())
}
}
return valueToDate
}
+ return undefined
}
export const convertToInt = (value: unknown): number => {
return result
}
+/**
+ * Generates a cryptographically secure random float in the [min, max] range
+ * @param max - The maximum value (inclusive). Defaults to `Number.MAX_VALUE`
+ * @param min - The minimum value (inclusive). Defaults to `0`
+ * @returns A float in the [min, max] range
+ */
export const getRandomFloat = (max = Number.MAX_VALUE, min = 0): number => {
if (max < min) {
throw new RangeError('Invalid interval')
}
- if (max - min === Number.POSITIVE_INFINITY) {
+ if (!Number.isFinite(max) || !Number.isFinite(min)) {
throw new RangeError('Invalid interval')
}
return (randomBytes(4).readUInt32LE() / 0xffffffff) * (max - min) + min
* @returns The rounded number.
*/
export const roundTo = (numberValue: number, scale: number): number => {
- const roundPower = 10 ** scale
- return Math.round(numberValue * roundPower * (1 + Number.EPSILON)) / roundPower
+ const factor = 10 ** scale
+ const scaled = numberValue * factor
+
+ const sign = Math.sign(scaled) || 1
+ const absScaled = Math.abs(scaled)
+ const integerPart = Math.trunc(absScaled)
+ const fractionalPart = absScaled - integerPart
+
+ const tol = Number.EPSILON * absScaled
+
+ const increment = fractionalPart > 0.5 + tol ? 1 : fractionalPart < 0.5 - tol ? 0 : 1
+
+ const roundedScaled = sign * (integerPart + increment)
+ return roundedScaled / factor
}
export const getRandomFloatRounded = (max = Number.MAX_VALUE, min = 0, scale = 2): number => {
- if (min !== 0) {
- return roundTo(getRandomFloat(max, min), scale)
- }
- return roundTo(getRandomFloat(max), scale)
+ return roundTo(getRandomFloat(max, min), scale)
}
export const getRandomFloatFluctuatedRounded = (
return roundTo(staticValue, scale)
}
const fluctuationRatio = fluctuationPercent / 100
- return getRandomFloatRounded(
- staticValue * (1 + fluctuationRatio),
- staticValue * (1 - fluctuationRatio),
- scale
- )
+ const upperValue = staticValue * (1 + fluctuationRatio)
+ const lowerValue = staticValue * (1 - fluctuationRatio)
+ const max = Math.max(upperValue, lowerValue)
+ const min = Math.min(upperValue, lowerValue)
+ return getRandomFloatRounded(max, min, scale)
}
export const extractTimeSeriesValues = (timeSeries: CircularBuffer<TimestampedData>): number[] => {
if (value instanceof Map) {
switch (mapFormat) {
case MapStringifyFormat.object:
- return {
- ...Object.fromEntries<Map<string, Record<string, unknown>>>(value.entries()),
- }
+ return Object.fromEntries<Record<string, unknown>>(value)
case MapStringifyFormat.array:
default:
return [...value]
expect(uuid).toBeDefined()
expect(uuid.length).toEqual(36)
expect(validateUUID(uuid)).toBe(true)
- expect(validateUUID('abcdef00-0000-4000-0000-000000000000')).toBe(true)
+ expect(validateUUID('abcdef00-0000-4000-0000-000000000000')).toBe(false)
expect(validateUUID('')).toBe(false)
// Shall invalidate Nil UUID
expect(validateUUID('00000000-0000-0000-0000-000000000000')).toBe(false)
expect(randomFloat).toBeLessThanOrEqual(Number.MAX_VALUE)
expect(randomFloat).not.toEqual(getRandomFloat())
expect(() => getRandomFloat(0, 1)).toThrow(new RangeError('Invalid interval'))
- expect(() => getRandomFloat(Number.MAX_VALUE, -Number.MAX_VALUE)).toThrow(
+ expect(() => getRandomFloat(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)).toThrow(
new RangeError('Invalid interval')
)
randomFloat = getRandomFloat(0, -Number.MAX_VALUE)