1 import { type FSWatcher
, readFileSync
, watch
} from
'node:fs'
2 import { dirname
, join
} from
'node:path'
3 import { env
} from
'node:process'
4 import { fileURLToPath
} from
'node:url'
6 import chalk from
'chalk'
7 import { merge
} from
'lodash-es'
10 buildPerformanceUriFilePath
,
11 checkWorkerElementsPerWorker
,
12 checkWorkerProcessType
,
13 getDefaultPerformanceStorageUri
,
16 } from
'./ConfigurationUtils.js'
17 import { Constants
} from
'./Constants.js'
18 import { hasOwnProp
, isCFEnvironment
, once
} from
'./Utils.js'
21 type ConfigurationData
,
24 type LogConfiguration
,
25 type StationTemplateUrl
,
26 type StorageConfiguration
,
28 SupervisionUrlDistribution
,
29 type UIServerConfiguration
,
30 type WorkerConfiguration
31 } from
'../types/index.js'
33 DEFAULT_ELEMENT_START_DELAY
,
34 DEFAULT_POOL_MAX_SIZE
,
35 DEFAULT_POOL_MIN_SIZE
,
36 DEFAULT_WORKER_START_DELAY
,
38 } from
'../worker/index.js'
40 type ConfigurationSectionType
=
42 | StorageConfiguration
44 | UIServerConfiguration
46 // eslint-disable-next-line @typescript-eslint/no-extraneous-class
47 export class Configuration
{
48 public static configurationChangeCallback
?: () => Promise
<void>
50 private static readonly configurationFile
= join(
51 dirname(fileURLToPath(import.meta
.url
)),
56 private static configurationFileReloading
= false
57 private static configurationData
?: ConfigurationData
58 private static configurationFileWatcher
?: FSWatcher
59 private static readonly configurationSectionCache
= new Map
<
61 ConfigurationSectionType
63 [ConfigurationSection
.log
, Configuration
.buildLogSection()],
64 [ConfigurationSection
.performanceStorage
, Configuration
.buildPerformanceStorageSection()],
65 [ConfigurationSection
.worker
, Configuration
.buildWorkerSection()],
66 [ConfigurationSection
.uiServer
, Configuration
.buildUIServerSection()]
69 private constructor () {
70 // This is intentional
73 public static getConfigurationSection
<T
extends ConfigurationSectionType
>(
74 sectionName
: ConfigurationSection
76 if (!Configuration
.isConfigurationSectionCached(sectionName
)) {
77 Configuration
.cacheConfigurationSection(sectionName
)
79 return Configuration
.configurationSectionCache
.get(sectionName
) as T
82 public static getStationTemplateUrls (): StationTemplateUrl
[] | undefined {
83 const checkDeprecatedConfigurationKeysOnce
= once(
84 Configuration
.checkDeprecatedConfigurationKeys
.bind(Configuration
),
87 checkDeprecatedConfigurationKeysOnce()
88 return Configuration
.getConfigurationData()?.stationTemplateUrls
91 public static getSupervisionUrls (): string | string[] | undefined {
93 Configuration
.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData
] != null
95 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
96 Configuration
.getConfigurationData()!.supervisionUrls
= Configuration
.getConfigurationData()![
97 'supervisionURLs' as keyof ConfigurationData
98 ] as string | string[]
100 return Configuration
.getConfigurationData()?.supervisionUrls
103 public static getSupervisionUrlDistribution (): SupervisionUrlDistribution
| undefined {
104 return hasOwnProp(Configuration
.getConfigurationData(), 'supervisionUrlDistribution')
105 ? Configuration
.getConfigurationData()?.supervisionUrlDistribution
106 : SupervisionUrlDistribution
.ROUND_ROBIN
109 public static workerPoolInUse (): boolean {
110 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.fixedPool
].includes(
111 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
112 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
117 public static workerDynamicPoolInUse (): boolean {
119 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
120 .processType
=== WorkerProcessType
.dynamicPool
124 private static isConfigurationSectionCached (sectionName
: ConfigurationSection
): boolean {
125 return Configuration
.configurationSectionCache
.has(sectionName
)
128 private static cacheConfigurationSection (sectionName
: ConfigurationSection
): void {
129 switch (sectionName
) {
130 case ConfigurationSection
.log
:
131 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildLogSection())
133 case ConfigurationSection
.performanceStorage
:
134 Configuration
.configurationSectionCache
.set(
136 Configuration
.buildPerformanceStorageSection()
139 case ConfigurationSection
.worker
:
140 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildWorkerSection())
142 case ConfigurationSection
.uiServer
:
143 Configuration
.configurationSectionCache
.set(
145 Configuration
.buildUIServerSection()
149 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
150 throw new Error(`Unknown configuration section '${sectionName}'`)
154 private static buildUIServerSection (): UIServerConfiguration
{
155 let uiServerConfiguration
: UIServerConfiguration
= {
157 type: ApplicationProtocol
.WS
,
159 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
160 port
: Constants
.DEFAULT_UI_SERVER_PORT
163 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.uiServer
)) {
164 uiServerConfiguration
= merge(
165 uiServerConfiguration
,
166 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
167 Configuration
.getConfigurationData()!.uiServer
!
170 if (isCFEnvironment()) {
171 delete uiServerConfiguration
.options
?.host
172 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
173 uiServerConfiguration
.options
!.port
= parseInt(env
.PORT
!)
175 return uiServerConfiguration
178 private static buildPerformanceStorageSection (): StorageConfiguration
{
179 let storageConfiguration
: StorageConfiguration
180 switch (Configuration
.getConfigurationData()?.performanceStorage
?.type) {
181 case StorageType
.SQLITE
:
182 storageConfiguration
= {
184 type: StorageType
.SQLITE
,
185 uri
: getDefaultPerformanceStorageUri(StorageType
.SQLITE
)
188 case StorageType
.JSON_FILE
:
189 storageConfiguration
= {
191 type: StorageType
.JSON_FILE
,
192 uri
: getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
)
195 case StorageType
.NONE
:
197 storageConfiguration
= {
199 type: StorageType
.NONE
203 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.performanceStorage
)) {
204 storageConfiguration
= {
205 ...storageConfiguration
,
206 ...Configuration
.getConfigurationData()?.performanceStorage
,
207 ...((Configuration
.getConfigurationData()?.performanceStorage
?.type ===
208 StorageType
.JSON_FILE
||
209 Configuration
.getConfigurationData()?.performanceStorage
?.type === StorageType
.SQLITE
) &&
210 Configuration
.getConfigurationData()?.performanceStorage
?.uri
!= null && {
211 uri
: buildPerformanceUriFilePath(
212 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
213 new URL(Configuration
.getConfigurationData()!.performanceStorage
!.uri
!).pathname
218 return storageConfiguration
221 private static buildLogSection (): LogConfiguration
{
222 const defaultLogConfiguration
: LogConfiguration
= {
224 file
: 'logs/combined.log',
225 errorFile
: 'logs/error.log',
226 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
231 const deprecatedLogConfiguration
: LogConfiguration
= {
232 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logEnabled') && {
233 enabled
: Configuration
.getConfigurationData()?.logEnabled
235 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFile') && {
236 file
: Configuration
.getConfigurationData()?.logFile
238 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logErrorFile') && {
239 errorFile
: Configuration
.getConfigurationData()?.logErrorFile
241 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logStatisticsInterval') && {
242 statisticsInterval
: Configuration
.getConfigurationData()?.logStatisticsInterval
244 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logLevel') && {
245 level
: Configuration
.getConfigurationData()?.logLevel
247 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logConsole') && {
248 console
: Configuration
.getConfigurationData()?.logConsole
250 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFormat') && {
251 format
: Configuration
.getConfigurationData()?.logFormat
253 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logRotate') && {
254 rotate
: Configuration
.getConfigurationData()?.logRotate
256 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxFiles') && {
257 maxFiles
: Configuration
.getConfigurationData()?.logMaxFiles
259 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxSize') && {
260 maxSize
: Configuration
.getConfigurationData()?.logMaxSize
263 const logConfiguration
: LogConfiguration
= {
264 ...defaultLogConfiguration
,
265 ...deprecatedLogConfiguration
,
266 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.log
) &&
267 Configuration
.getConfigurationData()?.log
)
269 return logConfiguration
272 private static buildWorkerSection (): WorkerConfiguration
{
273 const defaultWorkerConfiguration
: WorkerConfiguration
= {
274 processType
: WorkerProcessType
.workerSet
,
275 startDelay
: DEFAULT_WORKER_START_DELAY
,
276 elementsPerWorker
: 'auto',
277 elementStartDelay
: DEFAULT_ELEMENT_START_DELAY
,
278 poolMinSize
: DEFAULT_POOL_MIN_SIZE
,
279 poolMaxSize
: DEFAULT_POOL_MAX_SIZE
281 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
282 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerProcess') && {
283 processType
: Configuration
.getConfigurationData()?.workerProcess
285 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerStartDelay') && {
286 startDelay
: Configuration
.getConfigurationData()?.workerStartDelay
288 ...(hasOwnProp(Configuration
.getConfigurationData(), 'chargingStationsPerWorker') && {
289 elementsPerWorker
: Configuration
.getConfigurationData()?.chargingStationsPerWorker
291 ...(hasOwnProp(Configuration
.getConfigurationData(), 'elementStartDelay') && {
292 elementStartDelay
: Configuration
.getConfigurationData()?.elementStartDelay
294 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMinSize') && {
295 poolMinSize
: Configuration
.getConfigurationData()?.workerPoolMinSize
297 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMaxSize') && {
298 poolMaxSize
: Configuration
.getConfigurationData()?.workerPoolMaxSize
301 hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolStrategy') &&
302 delete Configuration
.getConfigurationData()?.workerPoolStrategy
303 const workerConfiguration
: WorkerConfiguration
= {
304 ...defaultWorkerConfiguration
,
305 ...deprecatedWorkerConfiguration
,
306 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.worker
) &&
307 Configuration
.getConfigurationData()?.worker
)
309 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
310 checkWorkerProcessType(workerConfiguration
.processType
!)
311 checkWorkerElementsPerWorker(workerConfiguration
.elementsPerWorker
)
312 return workerConfiguration
315 private static checkDeprecatedConfigurationKeys (): void {
316 // connection timeout
317 Configuration
.warnDeprecatedConfigurationKey(
318 'autoReconnectTimeout',
320 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
322 Configuration
.warnDeprecatedConfigurationKey(
325 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
327 // connection retries
328 Configuration
.warnDeprecatedConfigurationKey(
329 'autoReconnectMaxRetries',
331 'Use it in charging station template instead'
333 // station template url(s)
334 Configuration
.warnDeprecatedConfigurationKey(
335 'stationTemplateURLs',
337 "Use 'stationTemplateUrls' instead"
339 Configuration
.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData
] !=
341 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
342 (Configuration
.getConfigurationData()!.stationTemplateUrls
=
343 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
344 Configuration
.getConfigurationData()![
345 'stationTemplateURLs' as keyof ConfigurationData
346 ] as StationTemplateUrl
[])
347 Configuration
.getConfigurationData()?.stationTemplateUrls
.forEach(
348 (stationTemplateUrl
: StationTemplateUrl
) => {
349 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
350 if (stationTemplateUrl
['numberOfStation' as keyof StationTemplateUrl
] != null) {
352 `${chalk.green(logPrefix())} ${chalk.red(
353 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
359 // supervision url(s)
360 Configuration
.warnDeprecatedConfigurationKey(
363 "Use 'supervisionUrls' instead"
365 // supervision urls distribution
366 Configuration
.warnDeprecatedConfigurationKey(
367 'distributeStationToTenantEqually',
369 "Use 'supervisionUrlDistribution' instead"
371 Configuration
.warnDeprecatedConfigurationKey(
372 'distributeStationsToTenantsEqually',
374 "Use 'supervisionUrlDistribution' instead"
377 Configuration
.warnDeprecatedConfigurationKey(
380 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
382 Configuration
.warnDeprecatedConfigurationKey(
385 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
387 Configuration
.warnDeprecatedConfigurationKey(
390 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
392 Configuration
.warnDeprecatedConfigurationKey(
393 'chargingStationsPerWorker',
395 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
397 Configuration
.warnDeprecatedConfigurationKey(
400 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
402 Configuration
.warnDeprecatedConfigurationKey(
405 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
407 Configuration
.warnDeprecatedConfigurationKey(
410 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
412 Configuration
.warnDeprecatedConfigurationKey(
415 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
417 Configuration
.warnDeprecatedConfigurationKey(
418 'workerPoolStrategy',
420 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
422 Configuration
.warnDeprecatedConfigurationKey(
424 ConfigurationSection
.worker
,
425 'Not publicly exposed to end users'
428 Configuration
.getConfigurationData()?.worker
?.processType
===
429 ('staticPool' as WorkerProcessType
)
432 `${chalk.green(logPrefix())} ${chalk.red(
433 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
438 Configuration
.warnDeprecatedConfigurationKey(
441 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
443 Configuration
.warnDeprecatedConfigurationKey(
446 `Use '${ConfigurationSection.log}' section to define the log file instead`
448 Configuration
.warnDeprecatedConfigurationKey(
451 `Use '${ConfigurationSection.log}' section to define the log error file instead`
453 Configuration
.warnDeprecatedConfigurationKey(
456 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
458 Configuration
.warnDeprecatedConfigurationKey(
459 'logStatisticsInterval',
461 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
463 Configuration
.warnDeprecatedConfigurationKey(
466 `Use '${ConfigurationSection.log}' section to define the log level instead`
468 Configuration
.warnDeprecatedConfigurationKey(
471 `Use '${ConfigurationSection.log}' section to define the log format instead`
473 Configuration
.warnDeprecatedConfigurationKey(
476 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
478 Configuration
.warnDeprecatedConfigurationKey(
481 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
483 Configuration
.warnDeprecatedConfigurationKey(
486 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
488 // performanceStorage section
489 Configuration
.warnDeprecatedConfigurationKey(
491 ConfigurationSection
.performanceStorage
,
495 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
497 `${chalk.green(logPrefix())} ${chalk.red(
498 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
504 private static warnDeprecatedConfigurationKey (
506 sectionName
?: string,
510 sectionName
!= null &&
511 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] != null &&
513 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
520 `${chalk.green(logPrefix())} ${chalk.red(
521 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
522 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
526 } else if (Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
] != null) {
528 `${chalk.green(logPrefix())} ${chalk.red(
529 `Deprecated configuration key
'${key}' usage$
{
530 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
537 private static getConfigurationData (): ConfigurationData
| undefined {
538 if (Configuration
.configurationData
== null) {
540 Configuration
.configurationData
= JSON
.parse(
541 readFileSync(Configuration
.configurationFile
, 'utf8')
542 ) as ConfigurationData
543 if (Configuration
.configurationFileWatcher
== null) {
544 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
548 Configuration
.configurationFile
,
549 FileType
.Configuration
,
550 error
as NodeJS
.ErrnoException
,
555 return Configuration
.configurationData
558 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
560 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
562 !Configuration
.configurationFileReloading
&&
563 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
564 filename
!.trim().length
> 0 &&
567 Configuration
.configurationFileReloading
= true
568 const consoleWarnOnce
= once(console
.warn
, this)
570 `${chalk.green(logPrefix())} ${chalk.yellow(
571 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
574 delete Configuration
.configurationData
575 Configuration
.configurationSectionCache
.clear()
576 if (Configuration
.configurationChangeCallback
!= null) {
577 Configuration
.configurationChangeCallback()
579 throw typeof error
=== 'string' ? new Error(error
) : error
582 Configuration
.configurationFileReloading
= false
585 Configuration
.configurationFileReloading
= false
591 Configuration
.configurationFile
,
592 FileType
.Configuration
,
593 error
as NodeJS
.ErrnoException
,