]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
chore(deps): get rid of rambda
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 22 Jun 2025 21:05:14 +0000 (23:05 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 22 Jun 2025 21:05:14 +0000 (23:05 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
17 files changed:
.vscode/settings.json
eslint.config.js
package.json
pnpm-lock.yaml
scripts/bundle.js
src/charging-station/ChargingStation.ts
src/charging-station/Helpers.ts
src/charging-station/SharedLRUCache.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/performance/PerformanceStatistics.ts
src/utils/Configuration.ts
src/utils/Constants.ts
src/utils/StatisticUtils.ts
src/utils/Utils.ts
src/utils/index.ts
src/worker/WorkerFactory.ts

index f6e594c718e46094a412b4ad9fe30fe1495daa91..7ca8bd7339ebe9a0026f597268def6e247f9f0f4 100644 (file)
@@ -52,7 +52,6 @@
     "poolifier",
     "postject",
     "preinstall",
-    "rambda",
     "recurrency",
     "RFID",
     "shutdowning",
index 7d90cc43f766283b3094868ff5e81d7bab808bea..52728b39b97821893f3389e887beca05057d5608 100644 (file)
@@ -43,7 +43,6 @@ export default defineConfig([
               'logform',
               'mnemonist',
               'poolifier',
-              'rambda',
               'measurand',
               'measurands',
               'mikro',
index 6b2771a4a94e6436872f17a6b706b283b4f282ab..18a0aaa669087e3be125676a8d32dc1f7108f48f 100644 (file)
     "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",
index 5863d61663558279dc659f7319f2c7d3471075b1..3de0115eb77406512847d7710168bcd9116e6d92 100644 (file)
@@ -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
index 35caeb5759d6eed953a4c2b89be82832027e98ee..a41b28ffc2516f0c5977b059311e399fbeb7082e 100644 (file)
@@ -27,7 +27,6 @@ await build({
     'mongodb',
     'node:*',
     'poolifier',
-    'rambda',
     'tar',
     'winston',
     'winston/*',
index 89f731451b2044df9f14c6c49a7c1dc7b987a795..eaa486da25017f77271f4f9de5e3ec8a21712004 100644 (file)
@@ -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<ChargingStationInfo>
     // Priority:
     // 1. charging station info from template
     // 2. charging station info from configuration file
index 41aae07a9ff584ac9e5542c50d7a8f5fa30c26e3..a93d78452086795ec09ccc935a1010275952018a 100644 (file)
@@ -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,
index 60d8820ba46406a0c1a27104c6cfcfdfeef77995..2a28f4323cf08a0f59807a3e24a3ef85eef28268 100644 (file)
@@ -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 {
index a0eea259f096db68baad9894ea8c23e5c9ffe8ac..649c057f24482a6799798a3fadb3144878014542 100644 (file)
@@ -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'
index 6ecf4fe84afd8a6f1e78e0438059553c3729537f..b3d58eaeb18ee576c2743e1524e631e30639e90e 100644 (file)
@@ -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,
index 9c16208b9b999ecd02c19c1f83075aaf24d7a43a..b4d8d31b6bfe4aca2de2cec4208d864f2f2e3123 100644 (file)
@@ -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
index 8db3679d17a917f4f226b9eb293a58d684d387b1..9a3eec8ad2eeef9b1ff39862fd38d522aba048cd 100644 (file)
@@ -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`
index 4fff7d7f25be5b456566f484c4936c4ac800f8b0..37025b3378d84a5b225e4b7e4451dfb797f4165b 100644 (file)
@@ -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<ChargingStationInfo> = Object.freeze({
+  static readonly DEFAULT_STATION_INFO: Readonly<Partial<ChargingStationInfo>> = Object.freeze({
     automaticTransactionGeneratorPersistentConfiguration: true,
     autoReconnectMaxRetries: -1,
     autoStart: true,
index 7f0784bd63dbd9576bdb15a9afbab38658248177..4b87465daec17fb4446924f19637f69e31009b09 100644 (file)
@@ -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)
index 57e7f7e341d18b14f0dd86243ed1c8317f265cf2..85196eefb0446fb0ed6d393ee053f69b400a9df3 100644 (file)
@@ -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, ...T[]]
+type ReadonlyNonEmptyArray<T> = 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 = <T extends (...args: any[]) => any>(fn: T): T => {
+  let hasBeenCalled = false
+  let result: ReturnType<T> | 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>
+    }
+    // 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<string, unknown>).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 = <T extends object>(target: T, source: Partial<T>): 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 = <T>(
   value: unknown
 ): value is NonEmptyArray<T> | ReadonlyNonEmptyArray<T> => {
-  return Array.isArray(value) && isNotEmpty<T>(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<string, unknown>) => {
-      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<string, unknown>[]
       }
       return value
     },
index c36574020949b4e2f9a66f10cf6acd3b34b99531..cc970bedf17a7d18bf521e0ce0480a587b1486de 100644 (file)
@@ -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,
index c2210fa122b31e0ac509856b92b9fcf8dba24534..3332d3da0d0397e6b81be62b6c417f79102c3e8b 100644 (file)
@@ -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<D extends WorkerData, R extends WorkerData>(
     workerScript: string,
     workerProcessType: WorkerProcessType,
-    workerOptions?: WorkerOptions
+    workerOptions?: Partial<WorkerOptions>
   ): WorkerAbstract<D, R> {
     if (!isMainThread) {
       throw new Error('Cannot get a worker implementation outside the main thread')