import MemCache from 'node-cache' import DurationParser from 'parse-duration' import EnvironmentVar, { EnvironmentType } from './EnvironmentVar.js' // Hold cached values at nodeVM level // eslint-disable-next-line no-var /** * Creates a new instance of a memory cache store. * @returns {MemCache} A new instance of a memory cache store. */ let cacheStore = new MemCache() /** * Represents the schema for the configuration object. * @typedef {Object} ConfigurationSchema * @property {Object} [name] - The name of the configuration property. * @property {boolean} [name.isLocal] - Indicates if the property is for local use. * @property {boolean} [name.isRemote] - Indicates if the property is for remote use. * @property {boolean} [name.required] - Indicates if the property is required. * @property {string} [name.cachingPolicy] - The caching policy for the property. * @property {string} [name.nameOverride] - The optional name override if the key differs */ export type ConfigurationSchema = { [name: string]: { isLocal?: boolean isRemote?: boolean isSecure?: boolean required?: boolean noPrefix?: boolean cachingPolicy?: string nameOverride?: string } } /** * Extracts the properties from a given type that have the `isRemote` property set to `true`. * @param {Type} - The type to extract properties from. * @returns An object type with the extracted properties. */ // runtime type infer to ConfigurationSchema keys, which type is remote type ExtractRemote = { [Property in keyof Type]-?: Type[Property] extends { isRemote: true } ? Property : null } /** * Extracts the properties from a given type that have the `isLocal` property set to `true`. * @typeparam Type - The type to extract properties from. * @returns An object type with the extracted properties as keys and their corresponding types. */ // runtime type infer to ConfigurationSchema keys, which type is local type ExtractLocal = { [Property in keyof Type]-?: Type[Property] extends { isLocal: true } ? Property : null } /** * Creates a new type by omitting keys from type T whose values are of type U. * @param {T} - The original type * @param {U} - The type to omit from T * @returns A new type with the omitted keys */ // Helpers to filter null keys type OmitKeysByValueType = { [P in keyof T]: T[P] extends U ? never : P }[keyof T] /** * Omit properties from an object type by their value type. * @template T - The object type to omit properties from. * @template V - The value type of properties to omit. * @typedef OmitByValueType * @property {T} T - The object type to omit properties from. * @property {V} V - The value type of properties to omit. * @returns {OmitKeysByValueType} - The resulting object type after omitting properties. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, prettier/prettier type OmitByValueType = T extends infer _ ? { [key in OmitKeysByValueType]: T[key] } : never /** * Represents a configuration object with a specified schema and remote prefix. * @template T - The type of the configuration schema. */ export default class Configuration { /** * The schema for the private readonly property. * @type {T} */ private readonly schema: T /** * The prefix used for remote resources. * @type {string} */ private readonly remotePrefix: string /** * Indicates if config is local and should attempt to override * remote values from local. * @type {string} */ private static readonly isLocal: boolean = process.env.NODE_ENV == 'local' /** * Constructs a new instance of the class. * @param {T} schema - The schema object. * @param {string} remotePrefix - The remote prefix string. * @returns None */ constructor(schema: T, remotePrefix: string) { this.schema = schema this.remotePrefix = remotePrefix } /** * Retrieves the value of a property from the environment variables or cache. * @param {keyof OmitByValueType, null>} propName - The name of the property to retrieve. * @returns The value of the property. */ public get(propName: keyof OmitByValueType, null>): any { const propString = propName as string const propSchema = this.schema[propString] let v = this.getCachedValue(propString) v = v || new EnvironmentVar( propSchema.nameOverride || propString, EnvironmentType.Local, !propSchema.required, !propSchema.noPrefix ? this.remotePrefix : '' ).syncResolve() this.cacheValue(propString, v, propSchema.cachingPolicy) return v } /** * Asynchronously retrieves the value of a property from the remote environment. * @param {keyof OmitByValueType, null>} propName - The name of the property to retrieve. * @returns {Promise} - A promise that resolves to the value of the property. */ public async asyncGet(propName: keyof OmitByValueType, null>): Promise { const propString = propName as string const propSchema = this.schema[propString] // check cache let v = this.getCachedValue(propString) if (v !== undefined) return v // check for local override if (Configuration.isLocal && process.env[propString] !== undefined) { console.log(`Overriding remote variable ${propString} with local value!`) const v = process.env[propString] this.cacheValue(propString, v, propSchema.cachingPolicy) return v } // remote v = await new EnvironmentVar( propSchema.nameOverride || propString, propSchema.isSecure ? EnvironmentType.SecureRemote : EnvironmentType.PlainRemote, !propSchema.required, !propSchema.noPrefix ? this.remotePrefix : '' ).resolve() this.cacheValue(propString, v, propSchema.cachingPolicy) return v } /** * Retrieves the cached value associated with the given key from the cache store. * @param {string} valueKey - The key of the value to retrieve from the cache. * @returns The cached value associated with the given key, or undefined if the key does not exist in the cache. */ private getCachedValue(valueKey: string): any { return cacheStore.get(valueKey) } /** * Caches a value with the specified key and expiration policy. * @param {string} valueKey - The key to associate with the cached value. * @param {any} value - The value to be cached. * @param {string} [policy='1d'] - The expiration policy for the cached value. Defaults to '1d' (1 day). * @returns None */ private cacheValue(valueKey: string, value: any, policy: string = '1d'): void { cacheStore.set(valueKey, value, DurationParser(policy, 's') || '') } /** * Resets the cache by creating a new instance of the MemCache class and assigning it to the cacheStore variable. * @returns None */ private resetCache() { cacheStore = new MemCache() } }