From: Jérôme Benoit Date: Sun, 22 Jun 2025 21:05:14 +0000 (+0200) Subject: chore(deps): get rid of rambda X-Git-Tag: ocpp-server@v2.0.9~11 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=ff685bbbd579381e1964df1a25384b09ebb07028;p=e-mobility-charging-stations-simulator.git chore(deps): get rid of rambda Signed-off-by: Jérôme Benoit --- diff --git a/.vscode/settings.json b/.vscode/settings.json index f6e594c7..7ca8bd73 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,7 +52,6 @@ "poolifier", "postject", "preinstall", - "rambda", "recurrency", "RFID", "shutdowning", diff --git a/eslint.config.js b/eslint.config.js index 7d90cc43..52728b39 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -43,7 +43,6 @@ export default defineConfig([ 'logform', 'mnemonist', 'poolifier', - 'rambda', 'measurand', 'measurands', 'mikro', diff --git a/package.json b/package.json index 6b2771a4..18a0aaa6 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "mnemonist": "0.40.3", "mongodb": "^6.17.0", "poolifier": "^4.4.5", - "rambda": "^9.4.2", "tar": "^7.4.3", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5863d616..3de0115e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,9 +60,6 @@ importers: poolifier: specifier: ^4.4.5 version: 4.4.5 - rambda: - specifier: ^9.4.2 - version: 9.4.2 tar: specifier: ^7.4.3 version: 7.4.3 @@ -4923,9 +4920,6 @@ packages: resolution: {integrity: sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ==} hasBin: true - rambda@9.4.2: - resolution: {integrity: sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -11568,8 +11562,6 @@ snapshots: minimist: 1.2.8 through2: 2.0.5 - rambda@9.4.2: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 diff --git a/scripts/bundle.js b/scripts/bundle.js index 35caeb57..a41b28ff 100644 --- a/scripts/bundle.js +++ b/scripts/bundle.js @@ -27,7 +27,6 @@ await build({ 'mongodb', 'node:*', 'poolifier', - 'rambda', 'tar', 'winston', 'winston/*', diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 89f73145..eaa486da 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -7,7 +7,6 @@ import { existsSync, type FSWatcher, mkdirSync, readFileSync, rmSync, writeFileS import { dirname, join } from 'node:path' import { URL } from 'node:url' import { parentPort } from 'node:worker_threads' -import { mergeDeepRight, once } from 'rambda' import { type RawData, WebSocket } from 'ws' import { BaseError, OCPPError } from '../exception/index.js' @@ -93,7 +92,9 @@ import { isNotEmptyString, logger, logPrefix, + mergeDeepRight, min, + once, roundTo, secureRandom, sleep, @@ -1299,7 +1300,7 @@ export class ChargingStation extends EventEmitter { const stationInfoFromFile = this.getStationInfoFromFile( stationInfoFromTemplate.stationInfoPersistentConfiguration ) - let stationInfo: ChargingStationInfo + let stationInfo: Partial // Priority: // 1. charging station info from template // 2. charging station info from configuration file diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index 41aae07a..a93d7845 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -21,7 +21,6 @@ import { hash, randomBytes } from 'node:crypto' import { basename, dirname, isAbsolute, join, parse, relative, resolve } from 'node:path' import { env } from 'node:process' import { fileURLToPath } from 'node:url' -import { isEmpty } from 'rambda' import type { ChargingStation } from './ChargingStation.js' @@ -64,6 +63,7 @@ import { convertToInt, DCElectricUtils, isArraySorted, + isEmpty, isNotEmptyArray, isNotEmptyString, isValidDate, diff --git a/src/charging-station/SharedLRUCache.ts b/src/charging-station/SharedLRUCache.ts index 60d8820b..2a28f432 100644 --- a/src/charging-station/SharedLRUCache.ts +++ b/src/charging-station/SharedLRUCache.ts @@ -1,9 +1,8 @@ import { LRUMapWithDelete as LRUCache } from 'mnemonist' -import { isEmpty } from 'rambda' import type { ChargingStationConfiguration, ChargingStationTemplate } from '../types/index.js' -import { isNotEmptyArray, isNotEmptyString } from '../utils/index.js' +import { isEmpty, isNotEmptyArray, isNotEmptyString } from '../utils/index.js' import { Bootstrap } from './Bootstrap.js' enum CacheType { diff --git a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts index a0eea259..649c057f 100644 --- a/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts @@ -1,5 +1,4 @@ import { secondsToMilliseconds } from 'date-fns' -import { isEmpty } from 'rambda' import type { ChargingStation } from '../ChargingStation.js' @@ -39,7 +38,7 @@ import { type StopTransactionRequest, type StopTransactionResponse, } from '../../types/index.js' -import { Constants, convertToInt, isAsyncFunction, logger } from '../../utils/index.js' +import { Constants, convertToInt, isAsyncFunction, isEmpty, logger } from '../../utils/index.js' import { getConfigurationKey } from '../ConfigurationKeyUtils.js' import { buildMeterValue } from '../ocpp/index.js' import { WorkerBroadcastChannel } from './WorkerBroadcastChannel.js' diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 6ecf4fe8..b3d58eae 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -15,7 +15,6 @@ import { randomInt } from 'node:crypto' import { createWriteStream, readdirSync } from 'node:fs' import { dirname, extname, join, resolve } from 'node:path' import { fileURLToPath, URL } from 'node:url' -import { isEmpty } from 'rambda' import { create } from 'tar' import { @@ -108,6 +107,7 @@ import { formatDurationMilliSeconds, handleIncomingRequestError, isAsyncFunction, + isEmpty, isNotEmptyArray, isNotEmptyString, logger, diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index 9c16208b..b4d8d31b 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -6,7 +6,6 @@ import { secondsToMilliseconds } from 'date-fns' import { CircularBuffer } from 'mnemonist' import { performance, type PerformanceEntry, PerformanceObserver } from 'node:perf_hooks' import { parentPort } from 'node:worker_threads' -import { is, mean, median } from 'rambda' import { BaseError } from '../exception/index.js' import { @@ -32,6 +31,8 @@ import { logger, logPrefix, max, + mean, + median, min, nthPercentile, stdDeviation, @@ -81,7 +82,7 @@ export class PerformanceStatistics { try { performance.measure(name, markId) } catch (error) { - if (is(Error, error) && error.message.includes('performance mark has not been set')) { + if (error instanceof Error && error.message.includes('performance mark has not been set')) { /* Ignore */ } else { throw error diff --git a/src/utils/Configuration.ts b/src/utils/Configuration.ts index 8db3679d..9a3eec8a 100644 --- a/src/utils/Configuration.ts +++ b/src/utils/Configuration.ts @@ -3,7 +3,6 @@ import { existsSync, type FSWatcher, readFileSync, watch } from 'node:fs' import { dirname, join } from 'node:path' import { env } from 'node:process' import { fileURLToPath } from 'node:url' -import { has, mergeDeepRight, once } from 'rambda' import { ApplicationProtocol, @@ -35,7 +34,7 @@ import { logPrefix, } from './ConfigurationUtils.js' import { Constants } from './Constants.js' -import { isCFEnvironment } from './Utils.js' +import { has, isCFEnvironment, mergeDeepRight, once } from './Utils.js' type ConfigurationSectionType = | LogConfiguration @@ -169,7 +168,7 @@ export class Configuration { } public static getSupervisionUrlDistribution (): SupervisionUrlDistribution | undefined { - return has(Configuration.getConfigurationData(), 'supervisionUrlDistribution') + return has('supervisionUrlDistribution', Configuration.getConfigurationData()) ? Configuration.getConfigurationData()?.supervisionUrlDistribution : SupervisionUrlDistribution.ROUND_ROBIN } @@ -566,7 +565,7 @@ export class Configuration { "Use 'uri' instead" ) // uiServer section - if (has(Configuration.getConfigurationData(), 'uiWebSocketServer')) { + if (has('uiWebSocketServer', Configuration.getConfigurationData())) { console.error( `${chalk.green(logPrefix())} ${chalk.red( `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead` diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 4fff7d7f..37025b33 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -46,7 +46,7 @@ export class Constants { static readonly DEFAULT_PERFORMANCE_RECORDS_DB_NAME = 'e-mobility-charging-stations-simulator' static readonly DEFAULT_PERFORMANCE_RECORDS_FILENAME = 'performanceRecords.json' - static readonly DEFAULT_STATION_INFO: Partial = Object.freeze({ + static readonly DEFAULT_STATION_INFO: Readonly> = Object.freeze({ automaticTransactionGeneratorPersistentConfiguration: true, autoReconnectMaxRetries: -1, autoStart: true, diff --git a/src/utils/StatisticUtils.ts b/src/utils/StatisticUtils.ts index 7f0784bd..4b87465d 100644 --- a/src/utils/StatisticUtils.ts +++ b/src/utils/StatisticUtils.ts @@ -1,4 +1,21 @@ -import { mean } from 'rambda' +export const mean = (dataSet: number[]): number => { + if (Array.isArray(dataSet) && dataSet.length === 0) { + return 0 + } + return dataSet.reduce((accumulator, num) => accumulator + num, 0) / dataSet.length +} + +export const median = (dataSet: number[]): number => { + if (Array.isArray(dataSet) && dataSet.length === 0) { + return 0 + } + const sortedDataSet = dataSet.slice().sort((a, b) => a - b) + const length = sortedDataSet.length + if (length % 2 === 0) { + return (sortedDataSet[length / 2 - 1] + sortedDataSet[length / 2]) / 2 + } + return sortedDataSet[Math.floor(length / 2)] +} export const min = (...args: number[]): number => args.reduce((minimum, num) => (minimum < num ? minimum : num), Number.POSITIVE_INFINITY) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 57e7f7e3..85196eef 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -13,7 +13,6 @@ import { } from 'date-fns' import { getRandomValues, randomBytes, randomUUID } from 'node:crypto' import { env } from 'node:process' -import { is, isNotEmpty, type NonEmptyArray, type ReadonlyNonEmptyArray } from 'rambda' import { type JsonType, @@ -22,10 +21,87 @@ import { WebSocketCloseEventStatusString, } from '../types/index.js' +type NonEmptyArray = [T, ...T[]] +type ReadonlyNonEmptyArray = readonly [T, ...(readonly T[])] + export const logPrefix = (prefixString = ''): string => { return `${new Date().toLocaleString()}${prefixString}` } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const once = any>(fn: T): T => { + let hasBeenCalled = false + let result: ReturnType | undefined + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: any, ...args: Parameters): ReturnType { + if (!hasBeenCalled) { + hasBeenCalled = true + result = fn.apply(this, args) as ReturnType + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return result + } as T +} + +export const has = (property: PropertyKey, object: object | undefined): boolean => { + if (object == null) { + return false + } + return Object.prototype.hasOwnProperty.call(object, property) +} + +const type = (value: unknown): string => { + if (value === null) return 'Null' + if (value === undefined) return 'Undefined' + if (Number.isNaN(value)) return 'NaN' + if (Array.isArray(value)) return 'Array' + return Object.prototype.toString.call(value).slice(8, -1) +} + +export const isEmpty = (value: unknown): boolean => { + const inputType = type(value) + if (['NaN', 'Null', 'Number', 'Undefined'].includes(inputType)) { + return false + } + if (!value) return true + + if (inputType === 'Object') { + return Object.keys(value as Record).length === 0 + } + + if (inputType === 'Array') { + return (value as unknown[]).length === 0 + } + + return false +} + +const isObject = (value: unknown): value is object => { + return type(value) === 'Object' +} + +export const mergeDeepRight = (target: T, source: Partial): T => { + const output = { ...target } + + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach(key => { + if (isObject(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }) + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + output[key] = mergeDeepRight(target[key], source[key]) + } + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + Object.assign(output, { [key]: source[key] }) + } + }) + } + + return output +} + export const generateUUID = (): `${string}-${string}-${string}-${string}-${string}` => { return randomUUID() } @@ -236,7 +312,7 @@ export const isNotEmptyString = (value: unknown): value is NonEmptyString => { export const isNotEmptyArray = ( value: unknown ): value is NonEmptyArray | ReadonlyNonEmptyArray => { - return Array.isArray(value) && isNotEmpty(value as T[]) + return Array.isArray(value) && value.length > 0 } export const insertAt = (str: string, subStr: string, pos: number): string => @@ -277,7 +353,7 @@ export const JSONStringify = < return JSON.stringify( object, (_, value: Record) => { - if (is(Map, value)) { + if (value instanceof Map) { switch (mapFormat) { case MapStringifyFormat.object: return { @@ -287,8 +363,8 @@ export const JSONStringify = < default: return [...value] } - } else if (is(Set, value)) { - return [...value] + } else if (value instanceof Set) { + return [...value] as Record[] } return value }, diff --git a/src/utils/index.ts b/src/utils/index.ts index c3657402..cc970bed 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,7 +25,7 @@ export { buildStoppedMessage, buildUpdatedMessage, } from './MessageChannelUtils.js' -export { max, min, nthPercentile, stdDeviation } from './StatisticUtils.js' +export { max, mean, median, min, nthPercentile, stdDeviation } from './StatisticUtils.js' export { clone, convertToBoolean, @@ -40,13 +40,17 @@ export { getRandomFloatFluctuatedRounded, getRandomFloatRounded, getWebSocketCloseEventStatusString, + has, isArraySorted, isAsyncFunction, + isEmpty, isNotEmptyArray, isNotEmptyString, isValidDate, JSONStringify, logPrefix, + mergeDeepRight, + once, roundTo, secureRandom, sleep, diff --git a/src/worker/WorkerFactory.ts b/src/worker/WorkerFactory.ts index c2210fa1..3332d3da 100644 --- a/src/worker/WorkerFactory.ts +++ b/src/worker/WorkerFactory.ts @@ -1,8 +1,8 @@ import { isMainThread } from 'node:worker_threads' -import { mergeDeepRight } from 'rambda' import type { WorkerAbstract } from './WorkerAbstract.js' +import { mergeDeepRight } from '../utils/index.js' import { DEFAULT_WORKER_OPTIONS } from './WorkerConstants.js' import { WorkerDynamicPool } from './WorkerDynamicPool.js' import { WorkerFixedPool } from './WorkerFixedPool.js' @@ -18,7 +18,7 @@ export class WorkerFactory { public static getWorkerImplementation( workerScript: string, workerProcessType: WorkerProcessType, - workerOptions?: WorkerOptions + workerOptions?: Partial ): WorkerAbstract { if (!isMainThread) { throw new Error('Cannot get a worker implementation outside the main thread')