All files / src/Instance Configuration.ts

86% Statements 43/50
72.72% Branches 16/22
85.71% Functions 6/7
86% Lines 43/50

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225                                                                5x       5x           5x                     5x         5x 5x 5x                                                                                     6x 5x   6x           5x                 5x 5x   5x   5x       5x   5x   5x 5x         5x   5x                 5x   5x 60x                               3x 3x   1x   1x   3x                 13x 2x 11x   11x   11x 6x 5x     5x                 4x   4x 4x 3x   1x                   5x    
/**
 * Default Export is a singleton instance of Configuration class
 *
 * The Configuration class can be used to get settings as current values or as observables
 *
 * Settings can also be set though the Configuration class.
 *
 *
 *
 * @module Configuration
 * @category Configuration
 * @exports Configuration
 */
 
/**
 * The type definition for configuration settings.
 * Configuration settings can be a string, an object, or an array.
 * @typedef {string | Object | Array<any>} ConfigurationSetting
 */
import {
  defaultBalekConfiguration,
  BalekConfiguration,
} from "./Configuration/Defaults";
import { BehaviorSubject, Observable } from "rxjs";
import { MapObject } from "../Utility/DataStructures";
 
import { CommandLineArgumentsMap } from "./Configuration/ArgMap";
import { EnvironmentVariablesMap } from "./Configuration/EnvMap";
import * as fs from "node:fs";
/**
 * The path to the package.json file
 */
const packageFilePath = "./package.json";
/**
 * The path to the balek.json Configuration file
 */
let balekFilePath = "./config/balek.json";
/**
 * Saves the Balek Configuration to the balek.json file
 *
 * @param configuration The Configuration to save
 */
const saveBalekConfiguration = (configuration: BalekConfiguration) => {
  fs.writeFileSync(
    balekFilePath,
    JSON.stringify(configuration, null, 2),
    "utf-8"
  );
};
 
/**
 * Map to store the Configuration settings from the Balek Configuration file.
 */
const balekConfigurationMap = new Map<string, ConfigurationSetting>();
 
/**
 * Map to store the Configuration settings from the package.json file.
 */
const packageConfigurationMap = new Map<string, string | Object | Array<any>>();
const commandLineArguments = new CommandLineArgumentsMap();
const environmentArguments = new EnvironmentVariablesMap();
 
/**
 * The Configuration Class represents the main Configuration settings for the application.
 *
 * Loading settings from the environment, command line arguments, and Configuration files.
 *
 * This class offers both get and set methods to these settings.
 *
 * If a setting doesn't exist it will be created when set.
 *
 * It also provides a getObservable method for retrieving setting Observables that can be subscribed to.
 *
 *
 * @class Configuration
 *
 * @example
 * //import the Configuration class as Configuration
 * import Configuration from "../src/Instance/Configuration";
 * //get current value
 * const version = Configuration.get("version");
 * //get observable for setting
 * const portObservable = Configuration.getObservable("https.port");
 * //set the https port setting
 * Configuration.set("https.port", 443)
 *
 * @remarks
 * In the example, the Configuration class is imported as "Configuration". The current value of the version setting is retrieved using get().
 * An observable for the "https.port" setting is retrieved using getObservable().
 * The Configuration class is then used to set the "https.port" to 443.
 */
 
export class Configuration {
  /**
   * The singleton Configuration instance
   * @hidden
   */
  private static instance: Configuration;
  /**
   * Returns the singleton instance
   * @hidden
   */
  static getInstance() {
    if (!Configuration.instance) {
      Configuration.instance = new Configuration();
    }
    return Configuration.instance;
  }
  /**
   * Private Map of setting observables.
   * @hidden
   */
  private balekConfigurationObservableMap = new Map<
    string,
    BehaviorSubject<ConfigurationSetting>
  >();
  /**
   * Private constructor to prevent direct instantiation of the `Configuration` class.
   * @hidden
   */
  private constructor() {
    try {
      let packageData = { version: undefined, balek: undefined };
      //create empty packageData
      Eif (fs.existsSync(packageFilePath)) {
        //if package file exists then load it into packageData as JSON
        packageData = JSON.parse(
          fs.readFileSync(packageFilePath, "utf-8").toString()
        );
      }
      MapObject(packageData, packageConfigurationMap);
 
      let balekConfiguration = defaultBalekConfiguration;
      //check if balek Configuration file has been specified
      let specifiedConfigPath = this.get("config") as string;
      Iif (specifiedConfigPath) {
        //if it has, update the default
        balekFilePath = specifiedConfigPath;
      }
      //check is the balek Configuration file exists
      if (fs.existsSync(balekFilePath)) {
        //read the file parsing the JSON into the default values
        balekConfiguration = {
          ...balekConfiguration,
          ...JSON.parse(fs.readFileSync(balekFilePath, "utf-8").toString()),
        };
      } else E{
        //if the config file does not exist, create it with default values
        saveBalekConfiguration(balekConfiguration);
      }
      //Map the Configuration object into the Configuration map
      MapObject(balekConfiguration, balekConfigurationMap);
      //Create a Behaviour Subject for each mapped value
      balekConfigurationMap.forEach((setting, name) => {
        this.balekConfigurationObservableMap.set(
          name,
          new BehaviorSubject(setting)
        );
      });
    } catch (error) {
      throw error;
    }
  }
  /**
   * gets the rxjs Observable for the setting
   * @param {string} setting - name of setting for which the observable to retrieve
   * @returns {Observable<ConfigurationSetting>} - observable for the specified setting
   */
  getObservable(setting: string) {
    //get the observable for the setting
    let settingObservable = this.balekConfigurationObservableMap.get(setting);
    if (settingObservable === undefined) {
      //if the observable doesn't exist, create it
      settingObservable = new BehaviorSubject("" as ConfigurationSetting);
      //add it to tha map
      this.balekConfigurationObservableMap.set(setting, settingObservable);
    }
    return settingObservable.asObservable();
  }
  /**
   * Gets the value of the specified setting in order of the Balek Configuration Map,
   * Command Line Arguments Map, Package Configuration Map, or Environment Variables Map.
   * @param {string} setting - Setting name
   * @returns {(Object | string | Array<any> | undefined)} - Setting value or undefined
   */
  public get(setting: string): ConfigurationSetting | undefined {
    if (balekConfigurationMap.get(setting) !== undefined) {
      return balekConfigurationMap.get(setting);
    } else Iif (commandLineArguments.get(setting)) {
      return commandLineArguments.get(setting);
    } else Iif (packageConfigurationMap.get(`balek.${setting}`)) {
      return packageConfigurationMap.get(`balek.${setting}`);
    } else if (packageConfigurationMap.get(setting)) {
      return packageConfigurationMap.get(setting);
    } else Iif (environmentArguments.get(setting)) {
      return environmentArguments.get(setting);
    }
    return;
  }
  /**
   * set the value of the specified setting
   * @param {string} setting - setting for which the value is to be set
   * @param {ConfigurationSetting} value - value to be set for the specified setting
   */
  public set(setting: string, value: ConfigurationSetting) {
    //set the value in the Configuration Map
    balekConfigurationMap.set(setting, value);
    //get the setting BehaviorSubject
    let settingObservable = this.balekConfigurationObservableMap.get(setting);
    if (settingObservable) {
      settingObservable.next(value);
    } else {
      this.balekConfigurationObservableMap.set(
        setting,
        new BehaviorSubject(value)
      );
    }
  }
}
 
type ConfigurationSetting = string | Object | Array<any>;
 
const configuration = Configuration.getInstance();
export default configuration;