build(deps): apply updates
[e-mobility-charging-stations-simulator.git] / src / utils / Configuration.ts
index 6b8023ab0b2911cb7bf54ef38d21aaaddcfb0c67..31d994f58eee2c29d18cacee21ac3a565f708c57 100644 (file)
@@ -1,19 +1,21 @@
 import { type FSWatcher, readFileSync, watch } from 'node:fs';
-import { dirname, join, resolve } from 'node:path';
+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 { Constants } from './Constants';
 import {
-  hasOwnProp,
-  isCFEnvironment,
-  isNotEmptyString,
-  isUndefined,
+  buildPerformanceUriFilePath,
+  checkWorkerElementsPerWorker,
+  checkWorkerProcessType,
+  getDefaultPerformanceStorageUri,
+  handleFileException,
   logPrefix,
-  once,
-} from './Utils';
+} from './ConfigurationUtils';
+import { Constants } from './Constants';
+import { hasOwnProp, isCFEnvironment, isUndefined, once } from './Utils';
 import {
   ApplicationProtocol,
   type ConfigurationData,
@@ -50,6 +52,7 @@ export class Configuration {
     'config.json',
   );
 
+  private static configurationFileReloading = false;
   private static configurationData?: ConfigurationData;
   private static configurationFileWatcher?: FSWatcher;
   private static configurationSectionCache = new Map<
@@ -104,7 +107,7 @@ export class Configuration {
   }
 
   public static workerPoolInUse(): boolean {
-    return [WorkerProcessType.dynamicPool, WorkerProcessType.staticPool].includes(
+    return [WorkerProcessType.dynamicPool, WorkerProcessType.fixedPool].includes(
       Configuration.getConfigurationSection<WorkerConfiguration>(ConfigurationSection.worker)
         .processType!,
     );
@@ -117,10 +120,6 @@ export class Configuration {
     );
   }
 
-  private static logPrefix = (): string => {
-    return logPrefix(' Simulator configuration |');
-  };
-
   private static isConfigurationSectionCached(sectionName: ConfigurationSection): boolean {
     return Configuration.configurationSectionCache.has(sectionName);
   }
@@ -171,7 +170,7 @@ export class Configuration {
     }
     if (isCFEnvironment() === true) {
       delete uiServerConfiguration.options?.host;
-      uiServerConfiguration.options!.port = parseInt(process.env.PORT!);
+      uiServerConfiguration.options!.port = parseInt(env.PORT!);
     }
     return uiServerConfiguration;
   }
@@ -180,7 +179,7 @@ export class Configuration {
     let storageConfiguration: StorageConfiguration = {
       enabled: false,
       type: StorageType.JSON_FILE,
-      uri: Configuration.getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
+      uri: getDefaultPerformanceStorageUri(StorageType.JSON_FILE),
     };
     if (hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.performanceStorage)) {
       storageConfiguration = {
@@ -189,7 +188,7 @@ export class Configuration {
         ...(Configuration.getConfigurationData()?.performanceStorage?.type ===
           StorageType.JSON_FILE &&
           Configuration.getConfigurationData()?.performanceStorage?.uri && {
-            uri: Configuration.buildPerformanceUriFilePath(
+            uri: buildPerformanceUriFilePath(
               new URL(Configuration.getConfigurationData()!.performanceStorage!.uri!).pathname,
             ),
           }),
@@ -286,11 +285,8 @@ export class Configuration {
       ...(hasOwnProp(Configuration.getConfigurationData(), ConfigurationSection.worker) &&
         Configuration.getConfigurationData()?.worker),
     };
-    if (!Object.values(WorkerProcessType).includes(workerConfiguration.processType!)) {
-      throw new SyntaxError(
-        `Invalid worker process type '${workerConfiguration.processType}' defined in configuration`,
-      );
-    }
+    checkWorkerProcessType(workerConfiguration.processType!);
+    checkWorkerElementsPerWorker(workerConfiguration.elementsPerWorker);
     return workerConfiguration;
   }
 
@@ -329,7 +325,7 @@ export class Configuration {
       (stationTemplateUrl: StationTemplateUrl) => {
         if (!isUndefined(stationTemplateUrl?.['numberOfStation' as keyof StationTemplateUrl])) {
           console.error(
-            `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+            `${chalk.green(logPrefix())} ${chalk.red(
               `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`,
             )}`,
           );
@@ -385,17 +381,17 @@ export class Configuration {
       `Use '${ConfigurationSection.worker}' section to define the worker pool minimum size instead`,
     );
     Configuration.warnDeprecatedConfigurationKey(
-      'workerPoolSize;',
+      'workerPoolSize',
       undefined,
       `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
     );
     Configuration.warnDeprecatedConfigurationKey(
-      'workerPoolMaxSize;',
+      'workerPoolMaxSize',
       undefined,
       `Use '${ConfigurationSection.worker}' section to define the worker pool maximum size instead`,
     );
     Configuration.warnDeprecatedConfigurationKey(
-      'workerPoolStrategy;',
+      'workerPoolStrategy',
       undefined,
       `Use '${ConfigurationSection.worker}' section to define the worker pool strategy instead`,
     );
@@ -404,6 +400,16 @@ export class Configuration {
       ConfigurationSection.worker,
       'Not publicly exposed to end users',
     );
+    if (
+      Configuration.getConfigurationData()?.worker?.processType ===
+      ('staticPool' as WorkerProcessType)
+    ) {
+      console.error(
+        `${chalk.green(logPrefix())} ${chalk.red(
+          `Deprecated configuration 'staticPool' value usage in worker section 'processType' field. Use '${WorkerProcessType.fixedPool}' value instead`,
+        )}`,
+      );
+    }
     // log section
     Configuration.warnDeprecatedConfigurationKey(
       'logEnabled',
@@ -464,7 +470,7 @@ export class Configuration {
     // uiServer section
     if (hasOwnProp(Configuration.getConfigurationData(), 'uiWebSocketServer')) {
       console.error(
-        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+        `${chalk.green(logPrefix())} ${chalk.red(
           `Deprecated configuration section 'uiWebSocketServer' usage. Use '${ConfigurationSection.uiServer}' instead`,
         )}`,
       );
@@ -491,7 +497,7 @@ export class Configuration {
       )
     ) {
       console.error(
-        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+        `${chalk.green(logPrefix())} ${chalk.red(
           `Deprecated configuration key '${key}' usage in section '${sectionName}'${
             logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
           }`,
@@ -501,7 +507,7 @@ export class Configuration {
       !isUndefined(Configuration.getConfigurationData()?.[key as keyof ConfigurationData])
     ) {
       console.error(
-        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+        `${chalk.green(logPrefix())} ${chalk.red(
           `Deprecated configuration key '${key}' usage${
             logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
           }`,
@@ -520,11 +526,11 @@ export class Configuration {
           Configuration.configurationFileWatcher = Configuration.getConfigurationFileWatcher();
         }
       } catch (error) {
-        Configuration.handleFileException(
+        handleFileException(
           Configuration.configurationFile,
           FileType.Configuration,
           error as NodeJS.ErrnoException,
-          Configuration.logPrefix(),
+          logPrefix(),
         );
       }
     }
@@ -534,70 +540,40 @@ export class Configuration {
   private static getConfigurationFileWatcher(): FSWatcher | undefined {
     try {
       return watch(Configuration.configurationFile, (event, filename): void => {
-        if (filename!.trim()!.length > 0 && event === 'change') {
+        if (
+          !Configuration.configurationFileReloading &&
+          filename!.trim()!.length > 0 &&
+          event === 'change'
+        ) {
+          Configuration.configurationFileReloading = true;
+          const consoleWarnOnce = once(console.warn, this);
+          consoleWarnOnce(
+            `${chalk.green(logPrefix())} ${chalk.yellow(
+              `${FileType.Configuration} ${this.configurationFile} file have changed, reload`,
+            )}`,
+          );
           delete Configuration.configurationData;
           Configuration.configurationSectionCache.clear();
           if (!isUndefined(Configuration.configurationChangeCallback)) {
-            Configuration.configurationChangeCallback().catch((error) => {
-              throw typeof error === 'string' ? new Error(error) : error;
-            });
+            Configuration.configurationChangeCallback()
+              .catch((error) => {
+                throw typeof error === 'string' ? new Error(error) : error;
+              })
+              .finally(() => {
+                Configuration.configurationFileReloading = false;
+              });
+          } else {
+            Configuration.configurationFileReloading = false;
           }
         }
       });
     } catch (error) {
-      Configuration.handleFileException(
+      handleFileException(
         Configuration.configurationFile,
         FileType.Configuration,
         error as NodeJS.ErrnoException,
-        Configuration.logPrefix(),
+        logPrefix(),
       );
     }
   }
-
-  private static handleFileException(
-    file: string,
-    fileType: FileType,
-    error: NodeJS.ErrnoException,
-    logPfx: string,
-  ): void {
-    const prefix = isNotEmptyString(logPfx) ? `${logPfx} ` : '';
-    let logMsg: string;
-    switch (error.code) {
-      case 'ENOENT':
-        logMsg = `${fileType} file ${file} not found: `;
-        break;
-      case 'EEXIST':
-        logMsg = `${fileType} file ${file} already exists: `;
-        break;
-      case 'EACCES':
-        logMsg = `${fileType} file ${file} access denied: `;
-        break;
-      case 'EPERM':
-        logMsg = `${fileType} file ${file} permission denied: `;
-        break;
-      default:
-        logMsg = `${fileType} file ${file} error: `;
-    }
-    console.error(`${chalk.green(prefix)}${chalk.red(logMsg)}`, error);
-    throw error;
-  }
-
-  private static getDefaultPerformanceStorageUri(storageType: StorageType) {
-    switch (storageType) {
-      case StorageType.JSON_FILE:
-        return Configuration.buildPerformanceUriFilePath(
-          `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_FILENAME}`,
-        );
-      case StorageType.SQLITE:
-        return Configuration.buildPerformanceUriFilePath(
-          `${Constants.DEFAULT_PERFORMANCE_DIRECTORY}/${Constants.DEFAULT_PERFORMANCE_RECORDS_DB_NAME}.db`,
-        );
-      default:
-        throw new Error(`Unsupported storage type '${storageType}'`);
-    }
-  }
-
-  private static buildPerformanceUriFilePath(file: string) {
-    return `file://${join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), file)}`;
-  }
 }