import { getRandomValues, randomBytes, randomInt, randomUUID } from 'node:crypto'
+import { dirname, isAbsolute, join, parse, relative, resolve } from 'node:path'
import { env, nextTick } from 'node:process'
+import { fileURLToPath } from 'node:url'
import {
formatDuration,
} from 'date-fns'
import { Constants } from './Constants.js'
-import { type TimestampedData, WebSocketCloseEventStatusString } from '../types/index.js'
+import {
+ type EmptyObject,
+ type ProtocolResponse,
+ type TimestampedData,
+ WebSocketCloseEventStatusString
+} from '../types/index.js'
export const logPrefix = (prefixString = ''): string => {
return `${new Date().toLocaleString()}${prefixString}`
export const convertToDate = (
value: Date | string | number | undefined | null
-): Date | undefined | null => {
+): Date | undefined => {
if (value == null) {
- return value
+ return undefined
}
if (isDate(value)) {
return value
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)
}
+ let changedValue: number = value as number
if (isString(value)) {
changedValue = parseInt(value)
}
)
}
+export const buildTemplateName = (templateFile: string): string => {
+ if (isAbsolute(templateFile)) {
+ templateFile = relative(
+ resolve(join(dirname(fileURLToPath(import.meta.url)), 'assets', 'station-templates')),
+ templateFile
+ )
+ }
+ const templateFileParsedPath = parse(templateFile)
+ return join(templateFileParsedPath.dir, templateFileParsedPath.name)
+}
+
export const extractTimeSeriesValues = (timeSeries: TimestampedData[]): number[] => {
return timeSeries.map(timeSeriesItem => timeSeriesItem.value)
}
-export const isObject = (item: unknown): item is object => {
- 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 }
+/**
+ * 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 typeof fn === 'function' && fn.constructor.name === 'AsyncFunction'
+}
-type FormatKey = (key: string) => string
+export const isObject = (value: unknown): value is object => {
+ return value != null && typeof value === 'object' && !Array.isArray(value)
+}
-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
+export const isEmptyObject = (object: object): object is EmptyObject => {
+ if (object.constructor !== Object) {
+ return false
}
- 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
- )
+ // Iterates over the keys of an object, if
+ // any exist, return false.
+ // eslint-disable-next-line no-unreachable-loop
+ for (const _ in object) {
+ return false
}
- return clone as O
-}
-
-export const cloneObject = <T>(object: T): T => {
- return deepClone(object as CloneableData) as T
+ return true
}
-export const hasOwnProp = (object: unknown, property: PropertyKey): boolean => {
- return isObject(object) && Object.hasOwn(object, property)
+export const hasOwnProp = (value: unknown, property: PropertyKey): boolean => {
+ return isObject(value) && Object.hasOwn(value, property)
}
export const isCFEnvironment = (): boolean => {
return typeof value === 'string'
}
-export const isEmptyString = (value: unknown): value is string | undefined | null => {
+export const isEmptyString = (value: unknown): value is '' | undefined | null => {
return value == null || (isString(value) && value.trim().length === 0)
}
return isString(value) && value.trim().length > 0
}
-export const isEmptyArray = (object: unknown): object is unknown[] => {
- return Array.isArray(object) && object.length === 0
-}
-
-export const isNotEmptyArray = (object: unknown): object is unknown[] => {
- return Array.isArray(object) && object.length > 0
+export const isEmptyArray = (value: unknown): value is never[] => {
+ return Array.isArray(value) && value.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 =>
}
export const JSONStringifyWithMapSupport = (
- obj: Record<string, unknown> | Array<Record<string, unknown>> | Map<unknown, unknown>,
- space?: number
+ object:
+ | Record<string, unknown>
+ | Array<Record<string, unknown>>
+ | Map<unknown, unknown>
+ | ProtocolResponse,
+ space?: string | number
): string => {
return JSON.stringify(
- obj,
+ object,
(_, value: Record<string, unknown>) => {
if (value instanceof Map) {
return {