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
'just-merge'
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
] !==
96 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97 Configuration
.getConfigurationData()!.supervisionUrls
= Configuration
.getConfigurationData()![
98 'supervisionURLs' as keyof ConfigurationData
99 ] as string | string[]
101 return Configuration
.getConfigurationData()?.supervisionUrls
104 public static getSupervisionUrlDistribution (): SupervisionUrlDistribution
| undefined {
105 return hasOwnProp(Configuration
.getConfigurationData(), 'supervisionUrlDistribution')
106 ? Configuration
.getConfigurationData()?.supervisionUrlDistribution
107 : SupervisionUrlDistribution
.ROUND_ROBIN
110 public static workerPoolInUse (): boolean {
111 return [WorkerProcessType
.dynamicPool
, WorkerProcessType
.fixedPool
].includes(
112 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
113 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
118 public static workerDynamicPoolInUse (): boolean {
120 Configuration
.getConfigurationSection
<WorkerConfiguration
>(ConfigurationSection
.worker
)
121 .processType
=== WorkerProcessType
.dynamicPool
125 private static isConfigurationSectionCached (sectionName
: ConfigurationSection
): boolean {
126 return Configuration
.configurationSectionCache
.has(sectionName
)
129 private static cacheConfigurationSection (sectionName
: ConfigurationSection
): void {
130 switch (sectionName
) {
131 case ConfigurationSection
.log
:
132 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildLogSection())
134 case ConfigurationSection
.performanceStorage
:
135 Configuration
.configurationSectionCache
.set(
137 Configuration
.buildPerformanceStorageSection()
140 case ConfigurationSection
.worker
:
141 Configuration
.configurationSectionCache
.set(sectionName
, Configuration
.buildWorkerSection())
143 case ConfigurationSection
.uiServer
:
144 Configuration
.configurationSectionCache
.set(
146 Configuration
.buildUIServerSection()
150 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
151 throw new Error(`Unknown configuration section '${sectionName}'`)
155 private static buildUIServerSection (): UIServerConfiguration
{
156 let uiServerConfiguration
: UIServerConfiguration
= {
158 type: ApplicationProtocol
.WS
,
160 host
: Constants
.DEFAULT_UI_SERVER_HOST
,
161 port
: Constants
.DEFAULT_UI_SERVER_PORT
164 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.uiServer
)) {
165 uiServerConfiguration
= merge
<UIServerConfiguration
>(
166 uiServerConfiguration
,
167 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
168 Configuration
.getConfigurationData()!.uiServer
!
171 if (isCFEnvironment()) {
172 delete uiServerConfiguration
.options
?.host
173 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
174 uiServerConfiguration
.options
!.port
= parseInt(env
.PORT
!)
176 return uiServerConfiguration
179 private static buildPerformanceStorageSection (): StorageConfiguration
{
180 let storageConfiguration
: StorageConfiguration
181 switch (Configuration
.getConfigurationData()?.performanceStorage
?.type) {
182 case StorageType
.SQLITE
:
183 storageConfiguration
= {
185 type: StorageType
.SQLITE
,
186 uri
: getDefaultPerformanceStorageUri(StorageType
.SQLITE
)
189 case StorageType
.JSON_FILE
:
190 storageConfiguration
= {
192 type: StorageType
.JSON_FILE
,
193 uri
: getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
)
196 case StorageType
.NONE
:
198 storageConfiguration
= {
200 type: StorageType
.NONE
204 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.performanceStorage
)) {
205 storageConfiguration
= {
206 ...storageConfiguration
,
207 ...Configuration
.getConfigurationData()?.performanceStorage
,
208 ...((Configuration
.getConfigurationData()?.performanceStorage
?.type ===
209 StorageType
.JSON_FILE
||
210 Configuration
.getConfigurationData()?.performanceStorage
?.type === StorageType
.SQLITE
) &&
211 Configuration
.getConfigurationData()?.performanceStorage
?.uri
!= null && {
212 uri
: buildPerformanceUriFilePath(
213 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
214 new URL(Configuration
.getConfigurationData()!.performanceStorage
!.uri
!).pathname
219 return storageConfiguration
222 private static buildLogSection (): LogConfiguration
{
223 const defaultLogConfiguration
: LogConfiguration
= {
225 file
: 'logs/combined.log',
226 errorFile
: 'logs/error.log',
227 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
232 const deprecatedLogConfiguration
: LogConfiguration
= {
233 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logEnabled') && {
234 enabled
: Configuration
.getConfigurationData()?.logEnabled
236 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFile') && {
237 file
: Configuration
.getConfigurationData()?.logFile
239 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logErrorFile') && {
240 errorFile
: Configuration
.getConfigurationData()?.logErrorFile
242 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logStatisticsInterval') && {
243 statisticsInterval
: Configuration
.getConfigurationData()?.logStatisticsInterval
245 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logLevel') && {
246 level
: Configuration
.getConfigurationData()?.logLevel
248 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logConsole') && {
249 console
: Configuration
.getConfigurationData()?.logConsole
251 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFormat') && {
252 format
: Configuration
.getConfigurationData()?.logFormat
254 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logRotate') && {
255 rotate
: Configuration
.getConfigurationData()?.logRotate
257 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxFiles') && {
258 maxFiles
: Configuration
.getConfigurationData()?.logMaxFiles
260 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxSize') && {
261 maxSize
: Configuration
.getConfigurationData()?.logMaxSize
264 const logConfiguration
: LogConfiguration
= {
265 ...defaultLogConfiguration
,
266 ...deprecatedLogConfiguration
,
267 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.log
) &&
268 Configuration
.getConfigurationData()?.log
)
270 return logConfiguration
273 private static buildWorkerSection (): WorkerConfiguration
{
274 const defaultWorkerConfiguration
: WorkerConfiguration
= {
275 processType
: WorkerProcessType
.workerSet
,
276 startDelay
: DEFAULT_WORKER_START_DELAY
,
277 elementsPerWorker
: 'auto',
278 elementStartDelay
: DEFAULT_ELEMENT_START_DELAY
,
279 poolMinSize
: DEFAULT_POOL_MIN_SIZE
,
280 poolMaxSize
: DEFAULT_POOL_MAX_SIZE
282 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
283 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerProcess') && {
284 processType
: Configuration
.getConfigurationData()?.workerProcess
286 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerStartDelay') && {
287 startDelay
: Configuration
.getConfigurationData()?.workerStartDelay
289 ...(hasOwnProp(Configuration
.getConfigurationData(), 'chargingStationsPerWorker') && {
290 elementsPerWorker
: Configuration
.getConfigurationData()?.chargingStationsPerWorker
292 ...(hasOwnProp(Configuration
.getConfigurationData(), 'elementStartDelay') && {
293 elementStartDelay
: Configuration
.getConfigurationData()?.elementStartDelay
295 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMinSize') && {
296 poolMinSize
: Configuration
.getConfigurationData()?.workerPoolMinSize
298 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMaxSize') && {
299 poolMaxSize
: Configuration
.getConfigurationData()?.workerPoolMaxSize
302 hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolStrategy') &&
303 delete Configuration
.getConfigurationData()?.workerPoolStrategy
304 const workerConfiguration
: WorkerConfiguration
= {
305 ...defaultWorkerConfiguration
,
306 ...deprecatedWorkerConfiguration
,
307 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.worker
) &&
308 Configuration
.getConfigurationData()?.worker
)
310 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
311 checkWorkerProcessType(workerConfiguration
.processType
!)
312 checkWorkerElementsPerWorker(workerConfiguration
.elementsPerWorker
)
313 return workerConfiguration
316 private static checkDeprecatedConfigurationKeys (): void {
317 // connection timeout
318 Configuration
.warnDeprecatedConfigurationKey(
319 'autoReconnectTimeout',
321 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
323 Configuration
.warnDeprecatedConfigurationKey(
326 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
328 // connection retries
329 Configuration
.warnDeprecatedConfigurationKey(
330 'autoReconnectMaxRetries',
332 'Use it in charging station template instead'
334 // station template url(s)
335 Configuration
.warnDeprecatedConfigurationKey(
336 'stationTemplateURLs',
338 "Use 'stationTemplateUrls' instead"
340 Configuration
.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData
] !==
342 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
343 (Configuration
.getConfigurationData()!.stationTemplateUrls
=
344 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
345 Configuration
.getConfigurationData()![
346 'stationTemplateURLs' as keyof ConfigurationData
347 ] as StationTemplateUrl
[])
348 Configuration
.getConfigurationData()?.stationTemplateUrls
.forEach(
349 (stationTemplateUrl
: StationTemplateUrl
) => {
350 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
351 if (stationTemplateUrl
['numberOfStation' as keyof StationTemplateUrl
] !== undefined) {
353 `${chalk.green(logPrefix())} ${chalk.red(
354 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
360 // supervision url(s)
361 Configuration
.warnDeprecatedConfigurationKey(
364 "Use 'supervisionUrls' instead"
366 // supervision urls distribution
367 Configuration
.warnDeprecatedConfigurationKey(
368 'distributeStationToTenantEqually',
370 "Use 'supervisionUrlDistribution' instead"
372 Configuration
.warnDeprecatedConfigurationKey(
373 'distributeStationsToTenantsEqually',
375 "Use 'supervisionUrlDistribution' instead"
378 Configuration
.warnDeprecatedConfigurationKey(
381 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
383 Configuration
.warnDeprecatedConfigurationKey(
386 `Use '${ConfigurationSection.worker}' section to define the type of worker process model instead`
388 Configuration
.warnDeprecatedConfigurationKey(
391 `Use '${ConfigurationSection.worker}' section to define the worker start delay instead`
393 Configuration
.warnDeprecatedConfigurationKey(
394 'chargingStationsPerWorker',
396 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
398 Configuration
.warnDeprecatedConfigurationKey(
401 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
403 Configuration
.warnDeprecatedConfigurationKey(
406 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
408 Configuration
.warnDeprecatedConfigurationKey(
411 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
413 Configuration
.warnDeprecatedConfigurationKey(
416 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
418 Configuration
.warnDeprecatedConfigurationKey(
419 'workerPoolStrategy',
421 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
423 Configuration
.warnDeprecatedConfigurationKey(
425 ConfigurationSection
.worker
,
426 'Not publicly exposed to end users'
429 Configuration
.getConfigurationData()?.worker
?.processType
===
430 ('staticPool' as WorkerProcessType
)
433 `${chalk.green(logPrefix())} ${chalk.red(
434 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
439 Configuration
.warnDeprecatedConfigurationKey(
442 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
444 Configuration
.warnDeprecatedConfigurationKey(
447 `Use '${ConfigurationSection.log}' section to define the log file instead`
449 Configuration
.warnDeprecatedConfigurationKey(
452 `Use '${ConfigurationSection.log}' section to define the log error file instead`
454 Configuration
.warnDeprecatedConfigurationKey(
457 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
459 Configuration
.warnDeprecatedConfigurationKey(
460 'logStatisticsInterval',
462 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
464 Configuration
.warnDeprecatedConfigurationKey(
467 `Use '${ConfigurationSection.log}' section to define the log level instead`
469 Configuration
.warnDeprecatedConfigurationKey(
472 `Use '${ConfigurationSection.log}' section to define the log format instead`
474 Configuration
.warnDeprecatedConfigurationKey(
477 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
479 Configuration
.warnDeprecatedConfigurationKey(
482 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
484 Configuration
.warnDeprecatedConfigurationKey(
487 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
489 // performanceStorage section
490 Configuration
.warnDeprecatedConfigurationKey(
492 ConfigurationSection
.performanceStorage
,
496 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
498 `${chalk.green(logPrefix())} ${chalk.red(
499 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
505 private static warnDeprecatedConfigurationKey (
507 sectionName
?: string,
511 sectionName
!= null &&
512 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] !==
515 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
522 `${chalk.green(logPrefix())} ${chalk.red(
523 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
524 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
529 Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
] !== undefined
532 `${chalk.green(logPrefix())} ${chalk.red(
533 `Deprecated configuration key
'${key}' usage$
{
534 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
541 private static getConfigurationData (): ConfigurationData
| undefined {
542 if (Configuration
.configurationData
== null) {
544 Configuration
.configurationData
= JSON
.parse(
545 readFileSync(Configuration
.configurationFile
, 'utf8')
546 ) as ConfigurationData
547 if (Configuration
.configurationFileWatcher
== null) {
548 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
552 Configuration
.configurationFile
,
553 FileType
.Configuration
,
554 error
as NodeJS
.ErrnoException
,
559 return Configuration
.configurationData
562 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
564 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
566 !Configuration
.configurationFileReloading
&&
567 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
568 filename
!.trim().length
> 0 &&
571 Configuration
.configurationFileReloading
= true
572 const consoleWarnOnce
= once(console
.warn
, this)
574 `${chalk.green(logPrefix())} ${chalk.yellow(
575 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
578 delete Configuration
.configurationData
579 Configuration
.configurationSectionCache
.clear()
580 if (Configuration
.configurationChangeCallback
!== undefined) {
581 Configuration
.configurationChangeCallback()
583 throw typeof error
=== 'string' ? new Error(error
) : error
586 Configuration
.configurationFileReloading
= false
589 Configuration
.configurationFileReloading
= false
595 Configuration
.configurationFile
,
596 FileType
.Configuration
,
597 error
as NodeJS
.ErrnoException
,