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
:
191 storageConfiguration
= {
193 type: StorageType
.JSON_FILE
,
194 uri
: getDefaultPerformanceStorageUri(StorageType
.JSON_FILE
)
198 if (hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.performanceStorage
)) {
199 storageConfiguration
= {
200 ...storageConfiguration
,
201 ...Configuration
.getConfigurationData()?.performanceStorage
,
202 ...((Configuration
.getConfigurationData()?.performanceStorage
?.type ===
203 StorageType
.JSON_FILE
||
204 Configuration
.getConfigurationData()?.performanceStorage
?.type === StorageType
.SQLITE
) &&
205 Configuration
.getConfigurationData()?.performanceStorage
?.uri
!= null && {
206 uri
: buildPerformanceUriFilePath(
207 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
208 new URL(Configuration
.getConfigurationData()!.performanceStorage
!.uri
!).pathname
213 return storageConfiguration
216 private static buildLogSection (): LogConfiguration
{
217 const defaultLogConfiguration
: LogConfiguration
= {
219 file
: 'logs/combined.log',
220 errorFile
: 'logs/error.log',
221 statisticsInterval
: Constants
.DEFAULT_LOG_STATISTICS_INTERVAL
,
226 const deprecatedLogConfiguration
: LogConfiguration
= {
227 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logEnabled') && {
228 enabled
: Configuration
.getConfigurationData()?.logEnabled
230 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFile') && {
231 file
: Configuration
.getConfigurationData()?.logFile
233 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logErrorFile') && {
234 errorFile
: Configuration
.getConfigurationData()?.logErrorFile
236 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logStatisticsInterval') && {
237 statisticsInterval
: Configuration
.getConfigurationData()?.logStatisticsInterval
239 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logLevel') && {
240 level
: Configuration
.getConfigurationData()?.logLevel
242 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logConsole') && {
243 console
: Configuration
.getConfigurationData()?.logConsole
245 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logFormat') && {
246 format
: Configuration
.getConfigurationData()?.logFormat
248 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logRotate') && {
249 rotate
: Configuration
.getConfigurationData()?.logRotate
251 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxFiles') && {
252 maxFiles
: Configuration
.getConfigurationData()?.logMaxFiles
254 ...(hasOwnProp(Configuration
.getConfigurationData(), 'logMaxSize') && {
255 maxSize
: Configuration
.getConfigurationData()?.logMaxSize
258 const logConfiguration
: LogConfiguration
= {
259 ...defaultLogConfiguration
,
260 ...deprecatedLogConfiguration
,
261 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.log
) &&
262 Configuration
.getConfigurationData()?.log
)
264 return logConfiguration
267 private static buildWorkerSection (): WorkerConfiguration
{
268 const defaultWorkerConfiguration
: WorkerConfiguration
= {
269 processType
: WorkerProcessType
.workerSet
,
270 startDelay
: DEFAULT_WORKER_START_DELAY
,
271 elementsPerWorker
: 'auto',
272 elementStartDelay
: DEFAULT_ELEMENT_START_DELAY
,
273 poolMinSize
: DEFAULT_POOL_MIN_SIZE
,
274 poolMaxSize
: DEFAULT_POOL_MAX_SIZE
276 const deprecatedWorkerConfiguration
: WorkerConfiguration
= {
277 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerProcess') && {
278 processType
: Configuration
.getConfigurationData()?.workerProcess
280 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerStartDelay') && {
281 startDelay
: Configuration
.getConfigurationData()?.workerStartDelay
283 ...(hasOwnProp(Configuration
.getConfigurationData(), 'chargingStationsPerWorker') && {
284 elementsPerWorker
: Configuration
.getConfigurationData()?.chargingStationsPerWorker
286 ...(hasOwnProp(Configuration
.getConfigurationData(), 'elementStartDelay') && {
287 elementStartDelay
: Configuration
.getConfigurationData()?.elementStartDelay
289 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMinSize') && {
290 poolMinSize
: Configuration
.getConfigurationData()?.workerPoolMinSize
292 ...(hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolMaxSize') && {
293 poolMaxSize
: Configuration
.getConfigurationData()?.workerPoolMaxSize
296 hasOwnProp(Configuration
.getConfigurationData(), 'workerPoolStrategy') &&
297 delete Configuration
.getConfigurationData()?.workerPoolStrategy
298 const workerConfiguration
: WorkerConfiguration
= {
299 ...defaultWorkerConfiguration
,
300 ...deprecatedWorkerConfiguration
,
301 ...(hasOwnProp(Configuration
.getConfigurationData(), ConfigurationSection
.worker
) &&
302 Configuration
.getConfigurationData()?.worker
)
304 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
305 checkWorkerProcessType(workerConfiguration
.processType
!)
306 checkWorkerElementsPerWorker(workerConfiguration
.elementsPerWorker
)
307 return workerConfiguration
310 private static checkDeprecatedConfigurationKeys (): void {
311 // connection timeout
312 Configuration
.warnDeprecatedConfigurationKey(
313 'autoReconnectTimeout',
315 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
317 Configuration
.warnDeprecatedConfigurationKey(
320 "Use 'ConnectionTimeOut' OCPP parameter in charging station template instead"
322 // connection retries
323 Configuration
.warnDeprecatedConfigurationKey(
324 'autoReconnectMaxRetries',
326 'Use it in charging station template instead'
328 // station template url(s)
329 Configuration
.warnDeprecatedConfigurationKey(
330 'stationTemplateURLs',
332 "Use 'stationTemplateUrls' instead"
334 Configuration
.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData
] !==
336 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337 (Configuration
.getConfigurationData()!.stationTemplateUrls
=
338 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
339 Configuration
.getConfigurationData()![
340 'stationTemplateURLs' as keyof ConfigurationData
341 ] as StationTemplateUrl
[])
342 Configuration
.getConfigurationData()?.stationTemplateUrls
.forEach(
343 (stationTemplateUrl
: StationTemplateUrl
) => {
344 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
345 if (stationTemplateUrl
['numberOfStation' as keyof StationTemplateUrl
] !== undefined) {
347 `${chalk.green(logPrefix())} ${chalk.red(
348 `Deprecated configuration key
'numberOfStation' usage
for template file
'${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use
'numberOfStations' instead
`
354 // supervision url(s)
355 Configuration
.warnDeprecatedConfigurationKey(
358 "Use 'supervisionUrls' instead"
360 // supervision urls distribution
361 Configuration
.warnDeprecatedConfigurationKey(
362 'distributeStationToTenantEqually',
364 "Use 'supervisionUrlDistribution' instead"
366 Configuration
.warnDeprecatedConfigurationKey(
367 'distributeStationsToTenantsEqually',
369 "Use 'supervisionUrlDistribution' instead"
372 Configuration
.warnDeprecatedConfigurationKey(
375 `Use '${ConfigurationSection.worker}' section to define the type of worker process model 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 worker start delay instead`
387 Configuration
.warnDeprecatedConfigurationKey(
388 'chargingStationsPerWorker',
390 `Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
392 Configuration
.warnDeprecatedConfigurationKey(
395 `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
397 Configuration
.warnDeprecatedConfigurationKey(
400 `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`
402 Configuration
.warnDeprecatedConfigurationKey(
405 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
407 Configuration
.warnDeprecatedConfigurationKey(
410 `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`
412 Configuration
.warnDeprecatedConfigurationKey(
413 'workerPoolStrategy',
415 `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`
417 Configuration
.warnDeprecatedConfigurationKey(
419 ConfigurationSection
.worker
,
420 'Not publicly exposed to end users'
423 Configuration
.getConfigurationData()?.worker
?.processType
===
424 ('staticPool' as WorkerProcessType
)
427 `${chalk.green(logPrefix())} ${chalk.red(
428 `Deprecated configuration
'staticPool' value usage
in worker section
'processType' field
. Use
'${WorkerProcessType.fixedPool}' value instead
`
433 Configuration
.warnDeprecatedConfigurationKey(
436 `Use '${ConfigurationSection.log}' section to define the logging enablement instead`
438 Configuration
.warnDeprecatedConfigurationKey(
441 `Use '${ConfigurationSection.log}' section to define the log file instead`
443 Configuration
.warnDeprecatedConfigurationKey(
446 `Use '${ConfigurationSection.log}' section to define the log error file instead`
448 Configuration
.warnDeprecatedConfigurationKey(
451 `Use '${ConfigurationSection.log}' section to define the console logging enablement instead`
453 Configuration
.warnDeprecatedConfigurationKey(
454 'logStatisticsInterval',
456 `Use '${ConfigurationSection.log}' section to define the log statistics interval instead`
458 Configuration
.warnDeprecatedConfigurationKey(
461 `Use '${ConfigurationSection.log}' section to define the log level instead`
463 Configuration
.warnDeprecatedConfigurationKey(
466 `Use '${ConfigurationSection.log}' section to define the log format instead`
468 Configuration
.warnDeprecatedConfigurationKey(
471 `Use '${ConfigurationSection.log}' section to define the log rotation enablement instead`
473 Configuration
.warnDeprecatedConfigurationKey(
476 `Use '${ConfigurationSection.log}' section to define the log maximum files instead`
478 Configuration
.warnDeprecatedConfigurationKey(
481 `Use '${ConfigurationSection.log}' section to define the log maximum size instead`
483 // performanceStorage section
484 Configuration
.warnDeprecatedConfigurationKey(
486 ConfigurationSection
.performanceStorage
,
490 if (hasOwnProp(Configuration
.getConfigurationData(), 'uiWebSocketServer')) {
492 `${chalk.green(logPrefix())} ${chalk.red(
493 `Deprecated configuration section
'uiWebSocketServer' usage
. Use
'${ConfigurationSection.uiServer}' instead
`
499 private static warnDeprecatedConfigurationKey (
501 sectionName
?: string,
505 sectionName
!= null &&
506 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] !==
509 Configuration
.getConfigurationData()?.[sectionName
as keyof ConfigurationData
] as Record
<
516 `${chalk.green(logPrefix())} ${chalk.red(
517 `Deprecated configuration key
'${key}' usage
in section
'${sectionName}'$
{
518 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
523 Configuration
.getConfigurationData()?.[key
as keyof ConfigurationData
] !== undefined
526 `${chalk.green(logPrefix())} ${chalk.red(
527 `Deprecated configuration key
'${key}' usage$
{
528 logMsgToAppend
.trim().length
> 0 ? `. ${logMsgToAppend}` : ''
535 private static getConfigurationData (): ConfigurationData
| undefined {
536 if (Configuration
.configurationData
== null) {
538 Configuration
.configurationData
= JSON
.parse(
539 readFileSync(Configuration
.configurationFile
, 'utf8')
540 ) as ConfigurationData
541 if (Configuration
.configurationFileWatcher
== null) {
542 Configuration
.configurationFileWatcher
= Configuration
.getConfigurationFileWatcher()
546 Configuration
.configurationFile
,
547 FileType
.Configuration
,
548 error
as NodeJS
.ErrnoException
,
553 return Configuration
.configurationData
556 private static getConfigurationFileWatcher (): FSWatcher
| undefined {
558 return watch(Configuration
.configurationFile
, (event
, filename
): void => {
560 !Configuration
.configurationFileReloading
&&
561 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
562 filename
!.trim().length
> 0 &&
565 Configuration
.configurationFileReloading
= true
566 const consoleWarnOnce
= once(console
.warn
, this)
568 `${chalk.green(logPrefix())} ${chalk.yellow(
569 `${FileType.Configuration} ${this.configurationFile} file have changed
, reload
`
572 delete Configuration
.configurationData
573 Configuration
.configurationSectionCache
.clear()
574 if (Configuration
.configurationChangeCallback
!== undefined) {
575 Configuration
.configurationChangeCallback()
577 throw typeof error
=== 'string' ? new Error(error
) : error
580 Configuration
.configurationFileReloading
= false
583 Configuration
.configurationFileReloading
= false
589 Configuration
.configurationFile
,
590 FileType
.Configuration
,
591 error
as NodeJS
.ErrnoException
,