-import { type FSWatcher, readFileSync, watch } from 'node:fs'
+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 chalk from 'chalk'
-import merge from 'just-merge'
+import { mergeDeepRight, once } from 'rambda'
-import {
- buildPerformanceUriFilePath,
- checkWorkerElementsPerWorker,
- checkWorkerProcessType,
- getDefaultPerformanceStorageUri,
- handleFileException,
- logPrefix
-} from './ConfigurationUtils.js'
-import { Constants } from './Constants.js'
-import { hasOwnProp, isCFEnvironment, once } from './Utils.js'
import {
ApplicationProtocol,
+ ApplicationProtocolVersion,
type ConfigurationData,
ConfigurationSection,
FileType,
type WorkerConfiguration
} from '../types/index.js'
import {
- DEFAULT_ELEMENT_START_DELAY,
+ DEFAULT_ELEMENT_ADD_DELAY,
DEFAULT_POOL_MAX_SIZE,
DEFAULT_POOL_MIN_SIZE,
DEFAULT_WORKER_START_DELAY,
WorkerProcessType
} from '../worker/index.js'
+import {
+ buildPerformanceUriFilePath,
+ checkWorkerElementsPerWorker,
+ checkWorkerProcessType,
+ getDefaultPerformanceStorageUri,
+ handleFileException,
+ logPrefix
+} from './ConfigurationUtils.js'
+import { Constants } from './Constants.js'
+import { hasOwnProp, isCFEnvironment } from './Utils.js'
type ConfigurationSectionType =
| LogConfiguration
| WorkerConfiguration
| UIServerConfiguration
+const defaultUIServerConfiguration: UIServerConfiguration = {
+ enabled: false,
+ type: ApplicationProtocol.WS,
+ version: ApplicationProtocolVersion.VERSION_11,
+ options: {
+ host: Constants.DEFAULT_UI_SERVER_HOST,
+ port: Constants.DEFAULT_UI_SERVER_PORT
+ }
+}
+
+const defaultStorageConfiguration: StorageConfiguration = {
+ enabled: true,
+ type: StorageType.NONE
+}
+
+const defaultLogConfiguration: LogConfiguration = {
+ enabled: true,
+ file: 'logs/combined.log',
+ errorFile: 'logs/error.log',
+ statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
+ level: 'info',
+ format: 'simple',
+ rotate: true
+}
+
+const defaultWorkerConfiguration: WorkerConfiguration = {
+ processType: WorkerProcessType.workerSet,
+ startDelay: DEFAULT_WORKER_START_DELAY,
+ elementsPerWorker: 'auto',
+ elementAddDelay: DEFAULT_ELEMENT_ADD_DELAY,
+ poolMinSize: DEFAULT_POOL_MIN_SIZE,
+ poolMaxSize: DEFAULT_POOL_MAX_SIZE
+}
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class Configuration {
- public static configurationChangeCallback: () => Promise<void>
-
- private static readonly configurationFile = join(
- dirname(fileURLToPath(import.meta.url)),
- 'assets',
- 'config.json'
- )
+ public static configurationChangeCallback?: () => Promise<void>
+ private static configurationFile: string | undefined
private static configurationFileReloading = false
private static configurationData?: ConfigurationData
private static configurationFileWatcher?: FSWatcher
- private static readonly configurationSectionCache = new Map<
- ConfigurationSection,
- ConfigurationSectionType
- >([
- [ConfigurationSection.log, Configuration.buildLogSection()],
- [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
- [ConfigurationSection.worker, Configuration.buildWorkerSection()],
- [ConfigurationSection.uiServer, Configuration.buildUIServerSection()]
- ])
+ private static configurationSectionCache: Map<ConfigurationSection, ConfigurationSectionType>
+
+ static {
+ const configurationFile = join(dirname(fileURLToPath(import.meta.url)), 'assets', 'config.json')
+ if (existsSync(configurationFile)) {
+ Configuration.configurationFile = configurationFile
+ } else {
+ console.error(
+ `${chalk.green(logPrefix())} ${chalk.red(
+ `Configuration file '${configurationFile}' not found, using default configuration`
+ )}`
+ )
+ Configuration.configurationData = {
+ stationTemplateUrls: [],
+ supervisionUrls: 'ws://localhost:8180/steve/websocket/CentralSystemService',
+ supervisionUrlDistribution: SupervisionUrlDistribution.ROUND_ROBIN,
+ uiServer: defaultUIServerConfiguration,
+ performanceStorage: defaultStorageConfiguration,
+ log: defaultLogConfiguration,
+ worker: defaultWorkerConfiguration
+ }
+ }
+ Configuration.configurationSectionCache = new Map<
+ ConfigurationSection,
+ ConfigurationSectionType
+ >([
+ [ConfigurationSection.log, Configuration.buildLogSection()],
+ [ConfigurationSection.performanceStorage, Configuration.buildPerformanceStorageSection()],
+ [ConfigurationSection.worker, Configuration.buildWorkerSection()],
+ [ConfigurationSection.uiServer, Configuration.buildUIServerSection()]
+ ])
+ }
private constructor () {
// This is intentional
public static getStationTemplateUrls (): StationTemplateUrl[] | undefined {
const checkDeprecatedConfigurationKeysOnce = once(
- Configuration.checkDeprecatedConfigurationKeys.bind(Configuration),
- Configuration
+ Configuration.checkDeprecatedConfigurationKeys.bind(Configuration)
)
checkDeprecatedConfigurationKeysOnce()
return Configuration.getConfigurationData()?.stationTemplateUrls
public static getSupervisionUrls (): string | string[] | undefined {
if (
- Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] !==
- undefined
+ Configuration.getConfigurationData()?.['supervisionURLs' as keyof ConfigurationData] != null
) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Configuration.getConfigurationData()!.supervisionUrls = Configuration.getConfigurationData()![
}
private static buildUIServerSection (): UIServerConfiguration {
- let uiServerConfiguration: UIServerConfiguration = {
- enabled: false,
- type: ApplicationProtocol.WS,
- options: {
- host: Constants.DEFAULT_UI_SERVER_HOST,
- port: Constants.DEFAULT_UI_SERVER_PORT
- }
- }
+ let uiServerConfiguration: UIServerConfiguration = defaultUIServerConfiguration
if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.uiServer)) {
- uiServerConfiguration = merge<UIServerConfiguration>(
+ uiServerConfiguration = mergeDeepRight(
uiServerConfiguration,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Configuration.getConfigurationData()!.uiServer!
if (isCFEnvironment()) {
delete uiServerConfiguration.options?.host
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- uiServerConfiguration.options!.port = parseInt(env.PORT!)
+ uiServerConfiguration.options!.port = Number.parseInt(env.PORT!)
}
return uiServerConfiguration
}
private static buildPerformanceStorageSection (): StorageConfiguration {
- let storageConfiguration: StorageConfiguration = {
- enabled: false,
- type: StorageType.JSON_FILE,
- uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
+ let storageConfiguration: StorageConfiguration
+ switch (Configuration.getConfigurationData()?.performanceStorage?.type) {
+ case StorageType.SQLITE:
+ storageConfiguration = {
+ enabled: false,
+ type: StorageType.SQLITE,
+ uri: getDefaultPerformanceStorageUri(StorageType.SQLITE)
+ }
+ break
+ case StorageType.JSON_FILE:
+ storageConfiguration = {
+ enabled: false,
+ type: StorageType.JSON_FILE,
+ uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
+ }
+ break
+ case StorageType.NONE:
+ default:
+ storageConfiguration = defaultStorageConfiguration
+ break
}
if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
storageConfiguration = {
...storageConfiguration,
...Configuration.getConfigurationData()?.performanceStorage,
- ...(Configuration.getConfigurationData()?.performanceStorage?.type ===
- StorageType.JSON_FILE &&
+ ...((Configuration.getConfigurationData()?.performanceStorage?.type ===
+ StorageType.JSON_FILE ||
+ Configuration.getConfigurationData()?.performanceStorage?.type === StorageType.SQLITE) &&
Configuration.getConfigurationData()?.performanceStorage?.uri != null && {
uri: buildPerformanceUriFilePath(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
}
private static buildLogSection (): LogConfiguration {
- const defaultLogConfiguration: LogConfiguration = {
- enabled: true,
- file: 'logs/combined.log',
- errorFile: 'logs/error.log',
- statisticsInterval: Constants.DEFAULT_LOG_STATISTICS_INTERVAL,
- level: 'info',
- format: 'simple',
- rotate: true
- }
const deprecatedLogConfiguration: LogConfiguration = {
...(hasOwnProp(Configuration.getConfigurationData(), 'logEnabled') && {
enabled: Configuration.getConfigurationData()?.logEnabled
}
private static buildWorkerSection (): WorkerConfiguration {
- const defaultWorkerConfiguration: WorkerConfiguration = {
- processType: WorkerProcessType.workerSet,
- startDelay: DEFAULT_WORKER_START_DELAY,
- elementsPerWorker: 'auto',
- elementStartDelay: DEFAULT_ELEMENT_START_DELAY,
- poolMinSize: DEFAULT_POOL_MIN_SIZE,
- poolMaxSize: DEFAULT_POOL_MAX_SIZE
- }
const deprecatedWorkerConfiguration: WorkerConfiguration = {
...(hasOwnProp(Configuration.getConfigurationData(), 'workerProcess') && {
processType: Configuration.getConfigurationData()?.workerProcess
...(hasOwnProp(Configuration.getConfigurationData(), 'chargingStationsPerWorker') && {
elementsPerWorker: Configuration.getConfigurationData()?.chargingStationsPerWorker
}),
- ...(hasOwnProp(Configuration.getConfigurationData(), 'elementStartDelay') && {
- elementStartDelay: Configuration.getConfigurationData()?.elementStartDelay
+ ...(hasOwnProp(Configuration.getConfigurationData(), 'elementAddDelay') && {
+ elementAddDelay: Configuration.getConfigurationData()?.elementAddDelay
+ }),
+ ...(hasOwnProp(Configuration.getConfigurationData()?.worker, 'elementStartDelay') && {
+ elementAddDelay: Configuration.getConfigurationData()?.worker?.elementStartDelay
}),
...(hasOwnProp(Configuration.getConfigurationData(), 'workerPoolMinSize') && {
poolMinSize: Configuration.getConfigurationData()?.workerPoolMinSize
undefined,
"Use 'stationTemplateUrls' instead"
)
- Configuration.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData] !==
- undefined &&
+ Configuration.getConfigurationData()?.['stationTemplateURLs' as keyof ConfigurationData] !=
+ null &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(Configuration.getConfigurationData()!.stationTemplateUrls =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
] as StationTemplateUrl[])
Configuration.getConfigurationData()?.stationTemplateUrls.forEach(
(stationTemplateUrl: StationTemplateUrl) => {
- if (stationTemplateUrl?.['numberOfStation' as keyof StationTemplateUrl] !== undefined) {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (stationTemplateUrl['numberOfStation' as keyof StationTemplateUrl] != null) {
console.error(
`${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`
`Use '${ConfigurationSection.worker}' section to define the number of element(s) per worker instead`
)
Configuration.warnDeprecatedConfigurationKey(
- 'elementStartDelay',
+ 'elementAddDelay',
undefined,
- `Use '${ConfigurationSection.worker}' section to define the worker's element start delay instead`
+ `Use '${ConfigurationSection.worker}' section to define the worker's element add delay instead`
)
Configuration.warnDeprecatedConfigurationKey(
'workerPoolMinSize',
ConfigurationSection.worker,
'Not publicly exposed to end users'
)
+ Configuration.warnDeprecatedConfigurationKey(
+ 'elementStartDelay',
+ ConfigurationSection.worker,
+ "Use 'elementAddDelay' instead"
+ )
if (
Configuration.getConfigurationData()?.worker?.processType ===
('staticPool' as WorkerProcessType)
private static warnDeprecatedConfigurationKey (
key: string,
- sectionName?: string,
+ configurationSection?: ConfigurationSection,
logMsgToAppend = ''
): void {
if (
- sectionName != null &&
- Configuration.getConfigurationData()?.[sectionName as keyof ConfigurationData] !==
- undefined &&
+ configurationSection != null &&
+ Configuration.getConfigurationData()?.[configurationSection as keyof ConfigurationData] !=
+ null &&
(
- Configuration.getConfigurationData()?.[sectionName as keyof ConfigurationData] as Record<
- string,
- unknown
- >
- )?.[key] !== undefined
+ Configuration.getConfigurationData()?.[
+ configurationSection as keyof ConfigurationData
+ ] as Record<string, unknown>
+ )[key] != null
) {
console.error(
`${chalk.green(logPrefix())} ${chalk.red(
- `Deprecated configuration key '${key}' usage in section '${sectionName}'${
+ `Deprecated configuration key '${key}' usage in section '${configurationSection}'${
logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
}`
)}`
)
- } else if (
- Configuration.getConfigurationData()?.[key as keyof ConfigurationData] !== undefined
- ) {
+ } else if (Configuration.getConfigurationData()?.[key as keyof ConfigurationData] != null) {
console.error(
`${chalk.green(logPrefix())} ${chalk.red(
`Deprecated configuration key '${key}' usage${
}
}
- private static getConfigurationData (): ConfigurationData | undefined {
- if (Configuration.configurationData == null) {
+ public static getConfigurationData (): ConfigurationData | undefined {
+ if (
+ Configuration.configurationData == null &&
+ Configuration.configurationFile != null &&
+ Configuration.configurationFile.length > 0
+ ) {
try {
Configuration.configurationData = JSON.parse(
readFileSync(Configuration.configurationFile, 'utf8')
}
private static getConfigurationFileWatcher (): FSWatcher | undefined {
+ if (Configuration.configurationFile == null || Configuration.configurationFile.length === 0) {
+ return
+ }
try {
return watch(Configuration.configurationFile, (event, filename): void => {
if (
!Configuration.configurationFileReloading &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- filename!.trim()!.length > 0 &&
+ filename!.trim().length > 0 &&
event === 'change'
) {
Configuration.configurationFileReloading = true
- const consoleWarnOnce = once(console.warn, this)
+ const consoleWarnOnce = once(console.warn)
consoleWarnOnce(
`${chalk.green(logPrefix())} ${chalk.yellow(
`${FileType.Configuration} ${this.configurationFile} file have changed, reload`
)
delete Configuration.configurationData
Configuration.configurationSectionCache.clear()
- if (Configuration.configurationChangeCallback !== undefined) {
+ if (Configuration.configurationChangeCallback != null) {
Configuration.configurationChangeCallback()
- .catch((error) => {
+ .catch((error: unknown) => {
throw typeof error === 'string' ? new Error(error) : error
})
.finally(() => {