-import { getRandomValues, randomBytes, randomInt, randomUUID } from 'node:crypto'
+import { getRandomValues, randomBytes, randomUUID } from 'node:crypto'
import { env, nextTick } from 'node:process'
import {
minutesToSeconds,
secondsToMilliseconds
} from 'date-fns'
+import type { CircularBuffer } from 'mnemonist'
+import { is } from 'rambda'
-import { Constants } from './Constants.js'
-import { type TimestampedData, WebSocketCloseEventStatusString } from '../types/index.js'
+import {
+ type JsonType,
+ MapStringifyFormat,
+ type TimestampedData,
+ WebSocketCloseEventStatusString
+} from '../types/index.js'
export const logPrefix = (prefixString = ''): string => {
return `${new Date().toLocaleString()}${prefixString}`
}
-export const generateUUID = (): string => {
+export const generateUUID = (): `${string}-${string}-${string}-${string}-${string}` => {
return randomUUID()
}
-export const validateUUID = (uuid: string): boolean => {
+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)
}
export const sleep = async (milliSeconds: number): Promise<NodeJS.Timeout> => {
- return await new Promise<NodeJS.Timeout>((resolve) =>
+ return await new Promise<NodeJS.Timeout>(resolve =>
setTimeout(resolve as () => void, milliSeconds)
)
}
}
// More efficient time validation function than the one provided by date-fns
-export const isValidTime = (date: unknown): boolean => {
+export const isValidDate = (date: Date | number | undefined): date is Date | number => {
if (typeof date === 'number') {
return !isNaN(date)
} else if (isDate(date)) {
}
export const convertToDate = (
- value: Date | string | number | null | undefined
-): Date | null | undefined => {
+ value: Date | string | number | undefined | null
+): Date | undefined => {
if (value == null) {
- return value
+ return undefined
}
if (isDate(value)) {
return value
}
- if (isString(value) || typeof value === 'number') {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ if (typeof value === 'string' || typeof value === 'number') {
const valueToDate = new Date(value)
if (isNaN(valueToDate.getTime())) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
throw new Error(`Cannot convert to date: '${value}'`)
}
return valueToDate
if (value == null) {
return 0
}
- let changedValue: number = value as number
if (Number.isSafeInteger(value)) {
return value as number
}
if (typeof value === 'number') {
return Math.trunc(value)
}
- if (isString(value)) {
- changedValue = parseInt(value as string)
+ let changedValue: number = value as number
+ if (typeof value === 'string') {
+ changedValue = Number.parseInt(value)
}
if (isNaN(changedValue)) {
throw new Error(`Cannot convert to integer: '${String(value)}'`)
return 0
}
let changedValue: number = value as number
- if (isString(value)) {
- changedValue = parseFloat(value as string)
+ if (typeof value === 'string') {
+ changedValue = Number.parseFloat(value)
}
if (isNaN(changedValue)) {
throw new Error(`Cannot convert to float: '${String(value)}'`)
// Check the type
if (typeof value === 'boolean') {
return value
- } else if (isString(value) && ((value as string).toLowerCase() === 'true' || value === '1')) {
+ } else if (typeof value === 'string' && (value.toLowerCase() === 'true' || value === '1')) {
result = true
} else if (typeof value === 'number' && value === 1) {
result = true
if (max < min) {
throw new RangeError('Invalid interval')
}
- if (max - min === Infinity) {
+ if (max - min === Number.POSITIVE_INFINITY) {
throw new RangeError('Invalid interval')
}
return (randomBytes(4).readUInt32LE() / 0xffffffff) * (max - min) + min
}
-export const getRandomInteger = (max = Constants.MAX_RANDOM_INTEGER, min = 0): number => {
- max = Math.floor(max)
- if (min != null && min !== 0) {
- min = Math.ceil(min)
- return Math.floor(randomInt(min, max + 1))
- }
- return Math.floor(randomInt(max + 1))
-}
-
/**
* Rounds the given number to the given scale.
* The rounding is done using the "round half away from zero" method.
}
export const getRandomFloatRounded = (max = Number.MAX_VALUE, min = 0, scale = 2): number => {
- if (min != null && min !== 0) {
+ if (min !== 0) {
return roundTo(getRandomFloat(max, min), scale)
}
return roundTo(getRandomFloat(max), scale)
)
}
-export const extractTimeSeriesValues = (timeSeries: TimestampedData[]): number[] => {
- return timeSeries.map((timeSeriesItem) => timeSeriesItem.value)
+export const extractTimeSeriesValues = (timeSeries: CircularBuffer<TimestampedData>): number[] => {
+ return (timeSeries.toArray() as TimestampedData[]).map(timeSeriesItem => timeSeriesItem.value)
}
-export const isObject = (item: unknown): boolean => {
- return item != null && typeof item === 'object' && !Array.isArray(item)
+export const clone = <T>(object: T): T => {
+ return structuredClone<T>(object)
}
-type CloneableData =
- | number
- | string
- | boolean
- | null
- | undefined
- | Date
- | CloneableData[]
- | { [key: string]: CloneableData }
-
-type FormatKey = (key: string) => string
-
-const deepClone = <I extends CloneableData, O extends CloneableData = I>(
- value: I,
- formatKey?: FormatKey,
- refs: Map<I, O> = new Map<I, O>()
-): O => {
- const ref = refs.get(value)
- if (ref !== undefined) {
- return ref
- }
- if (Array.isArray(value)) {
- const clone: CloneableData[] = []
- refs.set(value, clone as O)
- for (let i = 0; i < value.length; i++) {
- clone[i] = deepClone(value[i], formatKey, refs)
- }
- return clone as O
- }
- if (value instanceof Date) {
- return new Date(value.valueOf()) as O
- }
- if (typeof value !== 'object' || value === null) {
- return value as unknown as O
- }
- const clone: Record<string, CloneableData> = {}
- refs.set(value, clone as O)
- for (const key of Object.keys(value)) {
- clone[typeof formatKey === 'function' ? formatKey(key) : key] = deepClone(
- value[key],
- formatKey,
- refs
- )
- }
- return clone as O
+/**
+ * Detects whether the given value is an asynchronous function or not.
+ *
+ * @param fn - Unknown value.
+ * @returns `true` if `fn` was an asynchronous function, otherwise `false`.
+ * @internal
+ */
+export const isAsyncFunction = (fn: unknown): fn is (...args: unknown[]) => Promise<unknown> => {
+ return is(Function, fn) && fn.constructor.name === 'AsyncFunction'
}
-export const cloneObject = <T>(object: T): T => {
- return deepClone(object as CloneableData) as T
+export const isObject = (value: unknown): value is object => {
+ return value != null && !Array.isArray(value) && is(Object, value)
}
-export const hasOwnProp = (object: unknown, property: PropertyKey): boolean => {
- return isObject(object) && Object.hasOwn(object as object, property)
+export const hasOwnProp = (value: unknown, property: PropertyKey): boolean => {
+ return isObject(value) && Object.hasOwn(value, property)
}
export const isCFEnvironment = (): boolean => {
return env.VCAP_APPLICATION != null
}
-export const isIterable = <T>(obj: T): boolean => {
- return obj != null ? typeof obj[Symbol.iterator as keyof T] === 'function' : false
-}
-
-const isString = (value: unknown): boolean => {
- return typeof value === 'string'
-}
-
-export const isEmptyString = (value: unknown): boolean => {
- return value == null || (isString(value) && (value as string).trim().length === 0)
-}
-
-export const isNotEmptyString = (value: unknown): boolean => {
- return isString(value) && (value as string).trim().length > 0
-}
-
-export const isEmptyArray = (object: unknown): boolean => {
- return Array.isArray(object) && object.length === 0
-}
-
-export const isNotEmptyArray = (object: unknown): boolean => {
- return Array.isArray(object) && object.length > 0
+export const isNotEmptyString = (value: unknown): value is string => {
+ return typeof value === 'string' && value.trim().length > 0
}
-export const isEmptyObject = (obj: object): boolean => {
- if (obj?.constructor !== Object) {
- return false
- }
- // Iterates over the keys of an object, if
- // any exist, return false.
- // eslint-disable-next-line no-unreachable-loop
- for (const _ in obj) {
- return false
- }
- return true
+export const isNotEmptyArray = (value: unknown): value is unknown[] => {
+ return Array.isArray(value) && value.length > 0
}
export const insertAt = (str: string, subStr: string, pos: number): string =>
return getRandomValues(new Uint32Array(1))[0] / 0x100000000
}
-export const JSONStringifyWithMapSupport = (
- obj: Record<string, unknown> | Array<Record<string, unknown>> | Map<unknown, unknown>,
- space?: number
-): string => {
+export const JSONStringify = <
+ T extends
+ | JsonType
+ | Array<Record<string, unknown>>
+ | Set<Record<string, unknown>>
+ | Map<string, Record<string, unknown>>
+>(
+ object: T,
+ space?: string | number,
+ mapFormat?: MapStringifyFormat
+ ): string => {
return JSON.stringify(
- obj,
+ object,
(_, value: Record<string, unknown>) => {
- if (value instanceof Map) {
- return {
- dataType: 'Map',
- value: [...value]
+ if (is(Map, value)) {
+ switch (mapFormat) {
+ case MapStringifyFormat.object:
+ return {
+ ...Object.fromEntries<Map<string, Record<string, unknown>>>(value.entries())
+ }
+ case MapStringifyFormat.array:
+ default:
+ return [...value]
}
+ } else if (is(Set, value)) {
+ return [...value] as JsonType[]
}
return value
},
}
}
if (
- WebSocketCloseEventStatusString[code as keyof typeof WebSocketCloseEventStatusString] !==
- undefined
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ WebSocketCloseEventStatusString[code as keyof typeof WebSocketCloseEventStatusString] != null
) {
return WebSocketCloseEventStatusString[code as keyof typeof WebSocketCloseEventStatusString]
}
return true
}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const once = <T, A extends any[], R>(
- fn: (...args: A) => R,
- context: T
-): ((...args: A) => R) => {
- let result: R
- return (...args: A) => {
- if (fn != null) {
- result = fn.apply<T, A, R>(context, args)
- ;(fn as unknown as undefined) = (context as unknown as undefined) = undefined
- }
- return result
- }
-}
-
-export const min = (...args: number[]): number =>
- args.reduce((minimum, num) => (minimum < num ? minimum : num), Infinity)
-
-export const max = (...args: number[]): number =>
- args.reduce((maximum, num) => (maximum > num ? maximum : num), -Infinity)
-
export const throwErrorInNextTick = (error: Error): void => {
nextTick(() => {
throw error