@avanio/variable-util-node
Advanced tools
| {"version":3,"file":"index.d.cts","names":[],"sources":["../src/AbstractFileRecordLoader.ts","../src/DockerSecretsConfigLoader.ts","../src/DotEnvLoader.ts","../src/FileConfigLoader.ts"],"mappings":";;;;;;;;AAoBA;;;UAAiB,+BAAA,kCAAiE,kBAAA;EACjF,QAAA,EAAU,QAAA;;EAEV,QAAA;;EAEA,QAAA;;EAEA,MAAA,EAAQ,WAAA;;EAER,KAAA;;EAEA,QAAA;;;;;;;;;;;;;AAuBD;EATC,QAAA,EAAU,gBAAA,CAAiB,MAAA,8BAAoC,MAAA;AAAA;;;;;;;uBAS1C,wBAAA,iBACL,+BAAA,WAA0C,+BAAA,8BACtC,cAAA,GAAiB,cAAA,UAC5B,eAAA,CAAgB,OAAA,EAAS,WAAA;EAAA,kBACT,UAAA,EAAY,SAAA;EAAA,mBAClB,cAAA,EAAgB,OAAA;EAAA,QAC3B,OAAA;EAAA,QACA,OAAA;EAER,WAAA,CAAmB,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,OAAA,IAAW,YAAA,GAAe,OAAA,CAAQ,WAAA;;;;EAQ/E,KAAA,CAAA,GAAsB,OAAA;EAAA,UAQN,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,WAAA;EAAA,UAU9C,UAAA,CAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,MAAA,8BAAoC,aAAA;EAAA,UAwBlE,cAAA,CAAA,GAAkB,OAAA;EAAA,QAe1B,eAAA;EAAA,QAeM,gBAAA;;;;qBAYK,WAAA,CAAY,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,OAAA,GAAU,MAAA,+BAAqC,OAAA,CAAQ,MAAA;AAAA;;;;;;AAvIjH;;;UCPiB,gCAAA;;EAEhB,aAAA;;EAEA,IAAA;;EAEA,QAAA;EDCiF;ECCjF,MAAA,EAAQ,WAAA;;EAER,QAAA;AAAA;;;;;;cAQY,yBAAA,qBAA8C,cAAA,GAAiB,cAAA,UAAwB,YAAA,CACnG,gCAAA,EACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,QACpB,aAAA;EAAA,UACE,cAAA,EAAgB,gCAAA;EAQ1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,gCAAA,IAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,UAAA,GAAY,SAAA;EAAA,UAMG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,WAAA;EAAA,QAoBtD,QAAA;AAAA;;;;;;ADtDT;;cEVa,YAAA,qBAAiC,cAAA,GAAiB,cAAA,UAAwB,wBAAA,CACtF,+BAAA,UACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,UAElB,cAAA,EAAgB,+BAAA;EAU1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,+BAAA,WAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,IAAA,GAAM,SAAA;EAAA,UAMG,WAAA,CAAY,OAAA,EAAS,MAAA,GAAS,MAAA;AAAA;;;;;;AFfzC;;cGXa,gBAAA,qBAAqC,cAAA,GAAiB,cAAA,UAAwB,wBAAA,CAC1F,+BAAA,UACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,UAElB,cAAA,EAAgB,+BAAA;EAU1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,+BAAA,YAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,IAAA,GAAM,SAAA;EAAA,UAMG,WAAA,CAAY,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,+BAAA,WAA0C,MAAA;;;;;;UAclF,2BAAA;AAAA"} |
| {"version":3,"file":"index.d.mts","names":[],"sources":["../src/AbstractFileRecordLoader.ts","../src/DockerSecretsConfigLoader.ts","../src/DotEnvLoader.ts","../src/FileConfigLoader.ts"],"mappings":";;;;;;;;AAoBA;;;UAAiB,+BAAA,kCAAiE,kBAAA;EACjF,QAAA,EAAU,QAAA;;EAEV,QAAA;;EAEA,QAAA;;EAEA,MAAA,EAAQ,WAAA;;EAER,KAAA;;EAEA,QAAA;;;;;;;;;;;;;AAuBD;EATC,QAAA,EAAU,gBAAA,CAAiB,MAAA,8BAAoC,MAAA;AAAA;;;;;;;uBAS1C,wBAAA,iBACL,+BAAA,WAA0C,+BAAA,8BACtC,cAAA,GAAiB,cAAA,UAC5B,eAAA,CAAgB,OAAA,EAAS,WAAA;EAAA,kBACT,UAAA,EAAY,SAAA;EAAA,mBAClB,cAAA,EAAgB,OAAA;EAAA,QAC3B,OAAA;EAAA,QACA,OAAA;EAER,WAAA,CAAmB,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,OAAA,IAAW,YAAA,GAAe,OAAA,CAAQ,WAAA;;;;EAQ/E,KAAA,CAAA,GAAsB,OAAA;EAAA,UAQN,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,WAAA;EAAA,UAU9C,UAAA,CAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,MAAA,8BAAoC,aAAA;EAAA,UAwBlE,cAAA,CAAA,GAAkB,OAAA;EAAA,QAe1B,eAAA;EAAA,QAeM,gBAAA;;;;qBAYK,WAAA,CAAY,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,OAAA,GAAU,MAAA,+BAAqC,OAAA,CAAQ,MAAA;AAAA;;;;;;AAvIjH;;;UCPiB,gCAAA;;EAEhB,aAAA;;EAEA,IAAA;;EAEA,QAAA;EDCiF;ECCjF,MAAA,EAAQ,WAAA;;EAER,QAAA;AAAA;;;;;;cAQY,yBAAA,qBAA8C,cAAA,GAAiB,cAAA,UAAwB,YAAA,CACnG,gCAAA,EACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,QACpB,aAAA;EAAA,UACE,cAAA,EAAgB,gCAAA;EAQ1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,gCAAA,IAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,UAAA,GAAY,SAAA;EAAA,UAMG,iBAAA,CAAkB,SAAA,WAAoB,OAAA,CAAQ,WAAA;EAAA,QAoBtD,QAAA;AAAA;;;;;;ADtDT;;cEVa,YAAA,qBAAiC,cAAA,GAAiB,cAAA,UAAwB,wBAAA,CACtF,+BAAA,UACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,UAElB,cAAA,EAAgB,+BAAA;EAU1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,+BAAA,WAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,IAAA,GAAM,SAAA;EAAA,UAMG,WAAA,CAAY,OAAA,EAAS,MAAA,GAAS,MAAA;AAAA;;;;;;AFfzC;;cGXa,gBAAA,qBAAqC,cAAA,GAAiB,cAAA,UAAwB,wBAAA,CAC1F,+BAAA,UACA,WAAA;EAAA,SAEgB,UAAA,EAAY,SAAA;EAAA,UAElB,cAAA,EAAgB,+BAAA;EAU1B,WAAA,CACC,OAAA,EAAS,QAAA,CAAS,OAAA,CAAQ,+BAAA,YAC1B,YAAA,GAAe,OAAA,CAAQ,WAAA,GACvB,IAAA,GAAM,SAAA;EAAA,UAMG,WAAA,CAAY,OAAA,EAAS,MAAA,EAAQ,OAAA,EAAS,+BAAA,WAA0C,MAAA;;;;;;UAclF,2BAAA;AAAA"} |
+72
-72
@@ -29,2 +29,5 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| //#endregion | ||
| let _avanio_variable_util = require("@avanio/variable-util"); | ||
| let _luolapeikko_result_option = require("@luolapeikko/result-option"); | ||
| let _luolapeikko_ts_common = require("@luolapeikko/ts-common"); | ||
| let fs = require("fs"); | ||
@@ -34,49 +37,4 @@ let fs_promises = require("fs/promises"); | ||
| path = __toESM(path); | ||
| let _avanio_variable_util = require("@avanio/variable-util"); | ||
| let _luolapeikko_ts_common = require("@luolapeikko/ts-common"); | ||
| let _luolapeikko_result_option = require("@luolapeikko/result-option"); | ||
| let dotenv = require("dotenv"); | ||
| //#region src/DockerSecretsConfigLoader.ts | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var DockerSecretsConfigLoader = class extends _avanio_variable_util.ConfigLoader { | ||
| loaderType; | ||
| valuePromises = /* @__PURE__ */ new Map(); | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileLowerCase: false, | ||
| isSilent: true, | ||
| logger: void 0, | ||
| path: "/run/secrets" | ||
| }; | ||
| constructor(options, overrideKeys, loaderType = "docker-secrets") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = loaderType; | ||
| } | ||
| async handleLoaderValue(lookupKey) { | ||
| const options = await this.getOptions(); | ||
| const filePath = this.filePath(lookupKey, options); | ||
| let valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(void 0); | ||
| if (!this.valuePromises.has(lookupKey)) { | ||
| if (!(0, fs.existsSync)(filePath)) { | ||
| if (!options.isSilent) throw new _avanio_variable_util.VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| options.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| } else valuePromise = (0, fs_promises.readFile)(filePath, "utf8"); | ||
| this.valuePromises.set(lookupKey, valuePromise); | ||
| } | ||
| return { | ||
| path: filePath, | ||
| value: await valuePromise | ||
| }; | ||
| } | ||
| filePath(key, options) { | ||
| return path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/AbstractFileRecordLoader.ts | ||
@@ -113,4 +71,4 @@ /** | ||
| return { | ||
| value: this.data.get(lookupKey), | ||
| path: fileName | ||
| path: fileName, | ||
| value: this.data.get(lookupKey) | ||
| }; | ||
@@ -170,41 +128,40 @@ } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.ts | ||
| //#region src/DockerSecretsConfigLoader.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var FileConfigLoader = class extends AbstractFileRecordLoader { | ||
| var DockerSecretsConfigLoader = class extends _avanio_variable_util.ConfigLoader { | ||
| loaderType; | ||
| valuePromises = /* @__PURE__ */ new Map(); | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileName: "config.json", | ||
| fileType: "json", | ||
| fileLowerCase: false, | ||
| isSilent: true, | ||
| logger: void 0, | ||
| validate: void 0, | ||
| watch: false | ||
| path: "/run/secrets" | ||
| }; | ||
| constructor(options, overrideKeys, type = "file") { | ||
| constructor(options, overrideKeys, loaderType = "docker-secrets") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = type; | ||
| this.loaderType = loaderType; | ||
| } | ||
| handleParse(rawData, options) { | ||
| const data = JSON.parse(rawData.toString()); | ||
| if (typeof data !== "object" || data === null || Array.isArray(data)) { | ||
| options.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`); | ||
| return {}; | ||
| async handleLoaderValue(lookupKey) { | ||
| const options = await this.getOptions(); | ||
| const filePath = this.filePath(lookupKey, options); | ||
| let valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(void 0); | ||
| if (!this.valuePromises.has(lookupKey)) { | ||
| if (!(0, fs.existsSync)(filePath)) { | ||
| if (!options.isSilent) throw new _avanio_variable_util.VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| options.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| } else valuePromise = (0, fs_promises.readFile)(filePath, "utf8"); | ||
| this.valuePromises.set(lookupKey, valuePromise); | ||
| } | ||
| return this.convertObjectToStringRecord(data); | ||
| return { | ||
| path: filePath, | ||
| value: await valuePromise | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| convertObjectToStringRecord(data) { | ||
| return Object.entries(data).reduce((acc, [key, value]) => { | ||
| if (_luolapeikko_ts_common.UndefCore.isNotNullish(value)) acc[key] = String(value); | ||
| return acc; | ||
| }, {}); | ||
| filePath(key, options) { | ||
| return path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key); | ||
| } | ||
@@ -241,2 +198,45 @@ }; | ||
| //#endregion | ||
| //#region src/FileConfigLoader.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var FileConfigLoader = class extends AbstractFileRecordLoader { | ||
| loaderType; | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileName: "config.json", | ||
| fileType: "json", | ||
| isSilent: true, | ||
| logger: void 0, | ||
| validate: void 0, | ||
| watch: false | ||
| }; | ||
| constructor(options, overrideKeys, type = "file") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = type; | ||
| } | ||
| handleParse(rawData, options) { | ||
| const data = JSON.parse(rawData.toString()); | ||
| if (typeof data !== "object" || data === null || Array.isArray(data)) { | ||
| options.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`); | ||
| return {}; | ||
| } | ||
| return this.convertObjectToStringRecord(data); | ||
| } | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| convertObjectToStringRecord(data) { | ||
| return Object.entries(data).reduce((acc, [key, value]) => { | ||
| if (_luolapeikko_ts_common.UndefCore.isNotNullish(value)) acc[key] = String(value); | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| }; | ||
| //#endregion | ||
| exports.AbstractFileRecordLoader = AbstractFileRecordLoader; | ||
@@ -243,0 +243,0 @@ exports.DockerSecretsConfigLoader = DockerSecretsConfigLoader; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.cjs","names":["ConfigLoader","VariableLookupError","MapConfigLoader","VariableError","ErrorCore","UndefCore"],"sources":["../src/DockerSecretsConfigLoader.ts","../src/AbstractFileRecordLoader.ts","../src/FileConfigLoader.ts","../src/DotEnvLoader.ts"],"sourcesContent":["import {existsSync} from 'fs';\nimport {readFile} from 'fs/promises';\nimport * as path from 'path';\nimport type {ILoggerLike} from '@avanio/logger-like';\nimport {ConfigLoader, type LoaderValue, type OverrideKeyMap, VariableLookupError} from '@avanio/variable-util';\nimport {type Loadable} from '@luolapeikko/ts-common';\n\nexport interface DockerSecretsConfigLoaderOptions {\n\t/** force file name to lower case */\n\tfileLowerCase: boolean;\n\t/** path to docker secrets, default is '/run/secrets' */\n\tpath: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to disable loader, default is false */\n\tdisabled: boolean;\n}\n\n/**\n * Loader for docker secrets, reads secrets from the `/run/secrets` directory.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<\n\tDockerSecretsConfigLoaderOptions,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\tprivate valuePromises = new Map<string, Promise<string | undefined>>();\n\tprotected defaultOptions: DockerSecretsConfigLoaderOptions = {\n\t\tdisabled: false,\n\t\tfileLowerCase: false,\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tpath: '/run/secrets',\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<DockerSecretsConfigLoaderOptions>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\tloaderType: Lowercase<string> = 'docker-secrets',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = loaderType;\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst options = await this.getOptions();\n\t\tconst filePath = this.filePath(lookupKey, options);\n\t\tlet valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(undefined);\n\t\tconst seen = this.valuePromises.has(lookupKey); // if valuePromise exists, it means we have seen this key before\n\t\tif (!seen) {\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tif (!options.isSilent) {\n\t\t\t\t\tthrow new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t\t}\n\t\t\t\toptions.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t} else {\n\t\t\t\tvaluePromise = readFile(filePath, 'utf8');\n\t\t\t}\n\t\t\t// store value promise as haven't seen this key before\n\t\t\tthis.valuePromises.set(lookupKey, valuePromise);\n\t\t}\n\t\treturn {path: filePath, value: await valuePromise};\n\t}\n\n\tprivate filePath(key: string, options: DockerSecretsConfigLoaderOptions): string {\n\t\treturn path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key);\n\t}\n}\n","import {existsSync, type FSWatcher, watch} from 'fs';\nimport {readFile} from 'fs/promises';\nimport type {ILoggerLike} from '@avanio/logger-like';\nimport {\n\tapplyStringMap,\n\ttype IConfigLoaderProps,\n\ttype LoaderValue,\n\tMapConfigLoader,\n\ttype OverrideKeyMap,\n\ttype ValidateCallback,\n\tVariableError,\n} from '@avanio/variable-util';\nimport {Err, type IResult, Ok} from '@luolapeikko/result-option';\nimport {ErrorCore, type Loadable} from '@luolapeikko/ts-common';\n\n/**\n * Options for the AbstractFileRecordLoader.\n * @template FileType Type of the file\n * @since v1.0.0\n */\nexport interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps {\n\tfileType: FileType;\n\t/** file name to load */\n\tfileName: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to watch file for changes */\n\twatch: boolean;\n\t/** set to true to disable loader */\n\tdisabled: boolean;\n\t/**\n\t * optional validator for data (Record<string, string | undefined>)\n\t * @example\n\t * // using zod\n\t * const stringRecordSchema = z.record(z.string().min(1), z.string());\n\t * const validate: ValidateCallback<Record<string, string>> = async (data) => {\n\t * const result = await stringRecordSchema.safeParseAsync(data);\n\t * if (!result.success) {\n\t * return {success: false, message: result.error.message};\n\t * }\n\t * return {success: true};\n\t * };\n\t */\n\tvalidate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined;\n}\n\n/**\n * Abstract class for loading records from a file.\n * @template Options Options for the loader\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport abstract class AbstractFileRecordLoader<\n\tOptions extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>,\n\tOverrideMap extends OverrideKeyMap = OverrideKeyMap,\n> extends MapConfigLoader<Options, OverrideMap> {\n\tabstract readonly loaderType: Lowercase<string>;\n\tprotected abstract defaultOptions: Options;\n\tprivate watcher: FSWatcher | undefined;\n\tprivate timeout: ReturnType<typeof setTimeout> | undefined;\n\n\tpublic constructor(options: Loadable<Partial<Options>>, overrideKeys?: Partial<OverrideMap>) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.handleFileChange = this.handleFileChange.bind(this);\n\t}\n\n\t/**\n\t * If the loader is watching the file, it will stop watching.\n\t */\n\tpublic async close(): Promise<void> {\n\t\tif (this.watcher) {\n\t\t\tconst {logger, fileName} = await this.getOptions();\n\t\t\tlogger?.debug(this.buildErrorStr(`closing file watcher for ${fileName}`));\n\t\t\tthis.watcher.close();\n\t\t}\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst {fileName} = await this.getOptions();\n\t\tif (!this._isLoaded) {\n\t\t\tawait this.loadData();\n\t\t\tthis._isLoaded = true; // only load data once to prevent spamming\n\t\t}\n\t\tconst value = this.data.get(lookupKey);\n\t\treturn {value, path: fileName};\n\t}\n\n\tprotected async handleData(): Promise<IResult<Record<string, string | undefined>, VariableError>> {\n\t\tconst options = await this.getOptions();\n\t\toptions.logger?.debug(this.buildErrorStr(`loading file ${options.fileName}`));\n\t\tif (!existsSync(options.fileName)) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} not found`)));\n\t\t}\n\t\tlet buffer;\n\t\ttry {\n\t\t\tbuffer = await readFile(options.fileName);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(ErrorCore.from(cause).message, {cause}));\n\t\t}\n\t\ttry {\n\t\t\tlet data = await this.handleParse(buffer, options);\n\t\t\tif (options.validate) {\n\t\t\t\tdata = await options.validate(data);\n\t\t\t}\n\t\t\tthis.handleFileWatch(options); // add watch after successful load\n\t\t\treturn Ok(data);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} is not a valid ${options.fileType}`), {cause}));\n\t\t}\n\t}\n\n\tprotected async handleLoadData(): Promise<boolean> {\n\t\tconst {logger, isSilent} = await this.getOptions();\n\t\tconst res = await this.handleData();\n\t\tif (res.isErr) {\n\t\t\tif (!isSilent) {\n\t\t\t\tres.unwrap();\n\t\t\t} else {\n\t\t\t\tlogger?.debug(res.err());\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tapplyStringMap(res.ok(), this.data);\n\t\treturn true;\n\t}\n\n\tprivate handleFileWatch(options: Options): void {\n\t\tif (options.watch && !this.watcher) {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`opening file watcher for ${options.fileName}`));\n\t\t\tthis.watcher = watch(options.fileName, () => {\n\t\t\t\tif (this.timeout) {\n\t\t\t\t\tclearTimeout(this.timeout);\n\t\t\t\t}\n\t\t\t\t// delay to prevent multiple reloads\n\t\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\t\tvoid this.handleFileChange(options);\n\t\t\t\t}, 200);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async handleFileChange(options: Options): Promise<void> {\n\t\ttry {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`file ${options.fileName} changed`));\n\t\t\tawait this.reload();\n\t\t} catch (err) {\n\t\t\toptions.logger?.error(this.buildErrorStr(`error reloading file ${options.fileName}: ${ErrorCore.from(err).message}`));\n\t\t}\n\t}\n\n\t/**\n\t * Handle the parsing of the file.\n\t */\n\tprotected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>;\n}\n","import {type OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable, UndefCore} from '@luolapeikko/ts-common';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * A file-based configuration loader that reads a JSON file.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<'json'>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'json'> = {\n\t\tdisabled: false,\n\t\tfileName: 'config.json',\n\t\tfileType: 'json',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'file',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined> {\n\t\tconst data: unknown = JSON.parse(rawData.toString());\n\t\tif (typeof data !== 'object' || data === null || Array.isArray(data)) {\n\t\t\toptions.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`);\n\t\t\treturn {};\n\t\t}\n\t\treturn this.convertObjectToStringRecord(data);\n\t}\n\n\t/**\n\t * Converts an object to a record of strings as env values are always strings.\n\t * @param {object} data The object to convert\n\t * @returns {Record<string, string>} The converted object\n\t */\n\tprivate convertObjectToStringRecord(data: object): Record<string, string> {\n\t\treturn Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {\n\t\t\tif (UndefCore.isNotNullish(value)) {\n\t\t\t\tacc[key] = String(value);\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {});\n\t}\n}\n","import {type OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable} from '@luolapeikko/ts-common';\nimport {parse} from 'dotenv';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * Loader for dotenv files, using the `dotenv` packages parser.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<string>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'env'> = {\n\t\tdisabled: false,\n\t\tfileName: '.env',\n\t\tfileType: 'env',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'dotenv',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer): Record<string, string | undefined> {\n\t\treturn parse(rawData);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,4BAAb,cAAoGA,mCAGlG;CACD,AAAgB;CAChB,AAAQ,gCAAgB,IAAI,KAA0C;CACtE,AAAU,iBAAmD;EAC5D,UAAU;EACV,eAAe;EACf,UAAU;EACV,QAAQ;EACR,MAAM;EACN;CAED,AAAO,YACN,SACA,cACA,aAAgC,kBAC/B;AACD,QAAM,SAAS,aAAa;AAC5B,OAAK,aAAa;;CAGnB,MAAgB,kBAAkB,WAAyC;EAC1E,MAAM,UAAU,MAAM,KAAK,YAAY;EACvC,MAAM,WAAW,KAAK,SAAS,WAAW,QAAQ;EAClD,IAAI,eAAe,KAAK,cAAc,IAAI,UAAU,IAAI,QAAQ,QAAQ,OAAU;AAElF,MAAI,CADS,KAAK,cAAc,IAAI,UAAU,EACnC;AACV,OAAI,oBAAY,SAAS,EAAE;AAC1B,QAAI,CAAC,QAAQ,SACZ,OAAM,IAAIC,0CAAoB,WAAW,mBAAmB,KAAK,WAAW,KAAK,UAAU,QAAQ,SAAS,YAAY;AAEzH,YAAQ,QAAQ,MAAM,mBAAmB,KAAK,WAAW,KAAK,UAAU,QAAQ,SAAS,YAAY;SAErG,0CAAwB,UAAU,OAAO;AAG1C,QAAK,cAAc,IAAI,WAAW,aAAa;;AAEhD,SAAO;GAAC,MAAM;GAAU,OAAO,MAAM;GAAa;;CAGnD,AAAQ,SAAS,KAAa,SAAmD;AAChF,SAAO,KAAK,KAAK,KAAK,QAAQ,QAAQ,KAAK,EAAE,QAAQ,gBAAgB,IAAI,aAAa,GAAG,IAAI;;;;;;;;;;;;ACf/F,IAAsB,2BAAtB,cAGUC,sCAAsC;CAG/C,AAAQ;CACR,AAAQ;CAER,AAAO,YAAY,SAAqC,cAAqC;AAC5F,QAAM,SAAS,aAAa;AAC5B,OAAK,mBAAmB,KAAK,iBAAiB,KAAK,KAAK;;;;;CAMzD,MAAa,QAAuB;AACnC,MAAI,KAAK,SAAS;GACjB,MAAM,EAAC,QAAQ,aAAY,MAAM,KAAK,YAAY;AAClD,WAAQ,MAAM,KAAK,cAAc,4BAA4B,WAAW,CAAC;AACzE,QAAK,QAAQ,OAAO;;;CAItB,MAAgB,kBAAkB,WAAyC;EAC1E,MAAM,EAAC,aAAY,MAAM,KAAK,YAAY;AAC1C,MAAI,CAAC,KAAK,WAAW;AACpB,SAAM,KAAK,UAAU;AACrB,QAAK,YAAY;;AAGlB,SAAO;GAAC,OADM,KAAK,KAAK,IAAI,UAAU;GACvB,MAAM;GAAS;;CAG/B,MAAgB,aAAkF;EACjG,MAAM,UAAU,MAAM,KAAK,YAAY;AACvC,UAAQ,QAAQ,MAAM,KAAK,cAAc,gBAAgB,QAAQ,WAAW,CAAC;AAC7E,MAAI,oBAAY,QAAQ,SAAS,CAChC,4CAAW,IAAIC,oCAAc,KAAK,cAAc,QAAQ,QAAQ,SAAS,YAAY,CAAC,CAAC;EAExF,IAAI;AACJ,MAAI;AACH,YAAS,gCAAe,QAAQ,SAAS;WACjC,OAAO;AACf,8CAAW,IAAIA,oCAAcC,iCAAU,KAAK,MAAM,CAAC,SAAS,EAAC,OAAM,CAAC,CAAC;;AAEtE,MAAI;GACH,IAAI,OAAO,MAAM,KAAK,YAAY,QAAQ,QAAQ;AAClD,OAAI,QAAQ,SACX,QAAO,MAAM,QAAQ,SAAS,KAAK;AAEpC,QAAK,gBAAgB,QAAQ;AAC7B,6CAAU,KAAK;WACP,OAAO;AACf,8CAAW,IAAID,oCAAc,KAAK,cAAc,QAAQ,QAAQ,SAAS,kBAAkB,QAAQ,WAAW,EAAE,EAAC,OAAM,CAAC,CAAC;;;CAI3H,MAAgB,iBAAmC;EAClD,MAAM,EAAC,QAAQ,aAAY,MAAM,KAAK,YAAY;EAClD,MAAM,MAAM,MAAM,KAAK,YAAY;AACnC,MAAI,IAAI,OAAO;AACd,OAAI,CAAC,SACJ,KAAI,QAAQ;OAEZ,SAAQ,MAAM,IAAI,KAAK,CAAC;AAEzB,UAAO;;AAER,4CAAe,IAAI,IAAI,EAAE,KAAK,KAAK;AACnC,SAAO;;CAGR,AAAQ,gBAAgB,SAAwB;AAC/C,MAAI,QAAQ,SAAS,CAAC,KAAK,SAAS;AACnC,WAAQ,QAAQ,MAAM,KAAK,cAAc,4BAA4B,QAAQ,WAAW,CAAC;AACzF,QAAK,wBAAgB,QAAQ,gBAAgB;AAC5C,QAAI,KAAK,QACR,cAAa,KAAK,QAAQ;AAG3B,SAAK,UAAU,iBAAiB;AAC/B,KAAK,KAAK,iBAAiB,QAAQ;OACjC,IAAI;KACN;;;CAIJ,MAAc,iBAAiB,SAAiC;AAC/D,MAAI;AACH,WAAQ,QAAQ,MAAM,KAAK,cAAc,QAAQ,QAAQ,SAAS,UAAU,CAAC;AAC7E,SAAM,KAAK,QAAQ;WACX,KAAK;AACb,WAAQ,QAAQ,MAAM,KAAK,cAAc,wBAAwB,QAAQ,SAAS,IAAIC,iCAAU,KAAK,IAAI,CAAC,UAAU,CAAC;;;;;;;;;;;;AC3IxH,IAAa,mBAAb,cAA2F,yBAGzF;CACD,AAAgB;CAEhB,AAAU,iBAA0D;EACnE,UAAU;EACV,UAAU;EACV,UAAU;EACV,UAAU;EACV,QAAQ;EACR,UAAU;EACV,OAAO;EACP;CAED,AAAO,YACN,SACA,cACA,OAA0B,QACzB;AACD,QAAM,SAAS,aAAa;AAC5B,OAAK,aAAa;;CAGnB,AAAU,YAAY,SAAiB,SAAsF;EAC5H,MAAM,OAAgB,KAAK,MAAM,QAAQ,UAAU,CAAC;AACpD,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,KAAK,EAAE;AACrE,WAAQ,QAAQ,MAAM,mBAAmB,KAAK,WAAW,4BAA4B,QAAQ,WAAW;AACxG,UAAO,EAAE;;AAEV,SAAO,KAAK,4BAA4B,KAAK;;;;;;;CAQ9C,AAAQ,4BAA4B,MAAsC;AACzE,SAAO,OAAO,QAAQ,KAAK,CAAC,QAAgC,KAAK,CAAC,KAAK,WAAW;AACjF,OAAIC,iCAAU,aAAa,MAAM,CAChC,KAAI,OAAO,OAAO,MAAM;AAEzB,UAAO;KACL,EAAE,CAAC;;;;;;;;;;;AC5CR,IAAa,eAAb,cAAuF,yBAGrF;CACD,AAAgB;CAEhB,AAAU,iBAAyD;EAClE,UAAU;EACV,UAAU;EACV,UAAU;EACV,UAAU;EACV,QAAQ;EACR,UAAU;EACV,OAAO;EACP;CAED,AAAO,YACN,SACA,cACA,OAA0B,UACzB;AACD,QAAM,SAAS,aAAa;AAC5B,OAAK,aAAa;;CAGnB,AAAU,YAAY,SAAqD;AAC1E,2BAAa,QAAQ"} | ||
| {"version":3,"file":"index.cjs","names":["MapConfigLoader","VariableError","ErrorCore","ConfigLoader","VariableLookupError","UndefCore"],"sources":["../src/AbstractFileRecordLoader.ts","../src/DockerSecretsConfigLoader.ts","../src/DotEnvLoader.ts","../src/FileConfigLoader.ts"],"sourcesContent":["import type {ILoggerLike} from '@avanio/logger-like';\nimport {\n\tapplyStringMap,\n\ttype IConfigLoaderProps,\n\ttype LoaderValue,\n\tMapConfigLoader,\n\ttype OverrideKeyMap,\n\ttype ValidateCallback,\n\tVariableError,\n} from '@avanio/variable-util';\nimport {Err, type IResult, Ok} from '@luolapeikko/result-option';\nimport {ErrorCore, type Loadable} from '@luolapeikko/ts-common';\nimport {existsSync, type FSWatcher, watch} from 'fs';\nimport {readFile} from 'fs/promises';\n\n/**\n * Options for the AbstractFileRecordLoader.\n * @template FileType Type of the file\n * @since v1.0.0\n */\nexport interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps {\n\tfileType: FileType;\n\t/** file name to load */\n\tfileName: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to watch file for changes */\n\twatch: boolean;\n\t/** set to true to disable loader */\n\tdisabled: boolean;\n\t/**\n\t * optional validator for data (Record<string, string | undefined>)\n\t * @example\n\t * // using zod\n\t * const stringRecordSchema = z.record(z.string().min(1), z.string());\n\t * const validate: ValidateCallback<Record<string, string>> = async (data) => {\n\t * const result = await stringRecordSchema.safeParseAsync(data);\n\t * if (!result.success) {\n\t * return {success: false, message: result.error.message};\n\t * }\n\t * return {success: true};\n\t * };\n\t */\n\tvalidate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined;\n}\n\n/**\n * Abstract class for loading records from a file.\n * @template Options Options for the loader\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport abstract class AbstractFileRecordLoader<\n\tOptions extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>,\n\tOverrideMap extends OverrideKeyMap = OverrideKeyMap,\n> extends MapConfigLoader<Options, OverrideMap> {\n\tpublic abstract readonly loaderType: Lowercase<string>;\n\tprotected abstract defaultOptions: Options;\n\tprivate watcher: FSWatcher | undefined;\n\tprivate timeout: ReturnType<typeof setTimeout> | undefined;\n\n\tpublic constructor(options: Loadable<Partial<Options>>, overrideKeys?: Partial<OverrideMap>) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.handleFileChange = this.handleFileChange.bind(this);\n\t}\n\n\t/**\n\t * If the loader is watching the file, it will stop watching.\n\t */\n\tpublic async close(): Promise<void> {\n\t\tif (this.watcher) {\n\t\t\tconst {logger, fileName} = await this.getOptions();\n\t\t\tlogger?.debug(this.buildErrorStr(`closing file watcher for ${fileName}`));\n\t\t\tthis.watcher.close();\n\t\t}\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst {fileName} = await this.getOptions();\n\t\tif (!this._isLoaded) {\n\t\t\tawait this.loadData();\n\t\t\tthis._isLoaded = true; // only load data once to prevent spamming\n\t\t}\n\t\tconst value = this.data.get(lookupKey);\n\t\treturn {path: fileName, value};\n\t}\n\n\tprotected async handleData(): Promise<IResult<Record<string, string | undefined>, VariableError>> {\n\t\tconst options = await this.getOptions();\n\t\toptions.logger?.debug(this.buildErrorStr(`loading file ${options.fileName}`));\n\t\tif (!existsSync(options.fileName)) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} not found`)));\n\t\t}\n\t\tlet buffer: Buffer;\n\t\ttry {\n\t\t\tbuffer = await readFile(options.fileName);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(ErrorCore.from(cause).message, {cause}));\n\t\t}\n\t\ttry {\n\t\t\tlet data = await this.handleParse(buffer, options);\n\t\t\tif (options.validate) {\n\t\t\t\tdata = await options.validate(data);\n\t\t\t}\n\t\t\tthis.handleFileWatch(options); // add watch after successful load\n\t\t\treturn Ok(data);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} is not a valid ${options.fileType}`), {cause}));\n\t\t}\n\t}\n\n\tprotected async handleLoadData(): Promise<boolean> {\n\t\tconst {logger, isSilent} = await this.getOptions();\n\t\tconst res = await this.handleData();\n\t\tif (res.isErr) {\n\t\t\tif (!isSilent) {\n\t\t\t\tres.unwrap();\n\t\t\t} else {\n\t\t\t\tlogger?.debug(res.err());\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tapplyStringMap(res.ok(), this.data);\n\t\treturn true;\n\t}\n\n\tprivate handleFileWatch(options: Options): void {\n\t\tif (options.watch && !this.watcher) {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`opening file watcher for ${options.fileName}`));\n\t\t\tthis.watcher = watch(options.fileName, () => {\n\t\t\t\tif (this.timeout) {\n\t\t\t\t\tclearTimeout(this.timeout);\n\t\t\t\t}\n\t\t\t\t// delay to prevent multiple reloads\n\t\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\t\tvoid this.handleFileChange(options);\n\t\t\t\t}, 200);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async handleFileChange(options: Options): Promise<void> {\n\t\ttry {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`file ${options.fileName} changed`));\n\t\t\tawait this.reload();\n\t\t} catch (err) {\n\t\t\toptions.logger?.error(this.buildErrorStr(`error reloading file ${options.fileName}: ${ErrorCore.from(err).message}`));\n\t\t}\n\t}\n\n\t/**\n\t * Handle the parsing of the file.\n\t */\n\tprotected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>;\n}\n","import type {ILoggerLike} from '@avanio/logger-like';\nimport {ConfigLoader, type LoaderValue, type OverrideKeyMap, VariableLookupError} from '@avanio/variable-util';\nimport type {Loadable} from '@luolapeikko/ts-common';\nimport {existsSync} from 'fs';\nimport {readFile} from 'fs/promises';\nimport * as path from 'path';\n\n/**\n * DockerSecretsConfigLoaderOptions is the interface for DockerSecretsConfigLoader options\n * @category Loaders\n * @since v1.0.0\n * @template OverrideMap - the type of the override key map\n */\nexport interface DockerSecretsConfigLoaderOptions {\n\t/** force file name to lower case */\n\tfileLowerCase: boolean;\n\t/** path to docker secrets, default is '/run/secrets' */\n\tpath: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to disable loader, default is false */\n\tdisabled: boolean;\n}\n\n/**\n * Loader for docker secrets, reads secrets from the `/run/secrets` directory.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<\n\tDockerSecretsConfigLoaderOptions,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\tprivate valuePromises = new Map<string, Promise<string | undefined>>();\n\tprotected defaultOptions: DockerSecretsConfigLoaderOptions = {\n\t\tdisabled: false,\n\t\tfileLowerCase: false,\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tpath: '/run/secrets',\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<DockerSecretsConfigLoaderOptions>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\tloaderType: Lowercase<string> = 'docker-secrets',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = loaderType;\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst options = await this.getOptions();\n\t\tconst filePath = this.filePath(lookupKey, options);\n\t\tlet valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(undefined);\n\t\tconst seen = this.valuePromises.has(lookupKey); // if valuePromise exists, it means we have seen this key before\n\t\tif (!seen) {\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tif (!options.isSilent) {\n\t\t\t\t\tthrow new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t\t}\n\t\t\t\toptions.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t} else {\n\t\t\t\tvaluePromise = readFile(filePath, 'utf8');\n\t\t\t}\n\t\t\t// store value promise as haven't seen this key before\n\t\t\tthis.valuePromises.set(lookupKey, valuePromise);\n\t\t}\n\t\treturn {path: filePath, value: await valuePromise};\n\t}\n\n\tprivate filePath(key: string, options: DockerSecretsConfigLoaderOptions): string {\n\t\treturn path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key);\n\t}\n}\n","import type {OverrideKeyMap} from '@avanio/variable-util';\nimport type {Loadable} from '@luolapeikko/ts-common';\nimport {parse} from 'dotenv';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * Loader for dotenv files, using the `dotenv` packages parser.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<string>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'env'> = {\n\t\tdisabled: false,\n\t\tfileName: '.env',\n\t\tfileType: 'env',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'dotenv',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer): Record<string, string | undefined> {\n\t\treturn parse(rawData);\n\t}\n}\n","import type {OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable, UndefCore} from '@luolapeikko/ts-common';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * A file-based configuration loader that reads a JSON file.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<'json'>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'json'> = {\n\t\tdisabled: false,\n\t\tfileName: 'config.json',\n\t\tfileType: 'json',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'file',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined> {\n\t\tconst data: unknown = JSON.parse(rawData.toString());\n\t\tif (typeof data !== 'object' || data === null || Array.isArray(data)) {\n\t\t\toptions.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`);\n\t\t\treturn {};\n\t\t}\n\t\treturn this.convertObjectToStringRecord(data);\n\t}\n\n\t/**\n\t * Converts an object to a record of strings as env values are always strings.\n\t * @param {object} data The object to convert\n\t * @returns {Record<string, string>} The converted object\n\t */\n\tprivate convertObjectToStringRecord(data: object): Record<string, string> {\n\t\treturn Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {\n\t\t\tif (UndefCore.isNotNullish(value)) {\n\t\t\t\tacc[key] = String(value);\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {});\n\t}\n}\n"],"mappings":"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"} |
+80
-74
| import { ILoggerLike } from "@avanio/logger-like"; | ||
| import { ConfigLoader, IConfigLoaderProps, LoaderValue, MapConfigLoader, OverrideKeyMap, ValidateCallback, VariableError } from "@avanio/variable-util"; | ||
| import { IResult } from "@luolapeikko/result-option"; | ||
| import { Loadable } from "@luolapeikko/ts-common"; | ||
| import { IResult } from "@luolapeikko/result-option"; | ||
| //#region src/DockerSecretsConfigLoader.d.ts | ||
| interface DockerSecretsConfigLoaderOptions { | ||
| /** force file name to lower case */ | ||
| fileLowerCase: boolean; | ||
| /** path to docker secrets, default is '/run/secrets' */ | ||
| path: string; | ||
| /** set to false if need errors */ | ||
| isSilent: boolean; | ||
| /** optional logger */ | ||
| logger: ILoggerLike | undefined; | ||
| /** set to true to disable loader, default is false */ | ||
| disabled: boolean; | ||
| } | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<DockerSecretsConfigLoaderOptions, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| private valuePromises; | ||
| protected defaultOptions: DockerSecretsConfigLoaderOptions; | ||
| constructor(options: Loadable<Partial<DockerSecretsConfigLoaderOptions>>, overrideKeys?: Partial<OverrideMap>, loaderType?: Lowercase<string>); | ||
| protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| private filePath; | ||
| } | ||
| //#endregion | ||
| //#region src/AbstractFileRecordLoader.d.ts | ||
| /** | ||
| * Options for the AbstractFileRecordLoader. | ||
| * @template FileType Type of the file | ||
| * @since v1.0.0 | ||
| */ | ||
| * Options for the AbstractFileRecordLoader. | ||
| * @template FileType Type of the file | ||
| * @since v1.0.0 | ||
| */ | ||
| interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps { | ||
@@ -52,22 +25,22 @@ fileType: FileType; | ||
| /** | ||
| * optional validator for data (Record<string, string | undefined>) | ||
| * @example | ||
| * // using zod | ||
| * const stringRecordSchema = z.record(z.string().min(1), z.string()); | ||
| * const validate: ValidateCallback<Record<string, string>> = async (data) => { | ||
| * const result = await stringRecordSchema.safeParseAsync(data); | ||
| * if (!result.success) { | ||
| * return {success: false, message: result.error.message}; | ||
| * } | ||
| * return {success: true}; | ||
| * }; | ||
| */ | ||
| * optional validator for data (Record<string, string | undefined>) | ||
| * @example | ||
| * // using zod | ||
| * const stringRecordSchema = z.record(z.string().min(1), z.string()); | ||
| * const validate: ValidateCallback<Record<string, string>> = async (data) => { | ||
| * const result = await stringRecordSchema.safeParseAsync(data); | ||
| * if (!result.success) { | ||
| * return {success: false, message: result.error.message}; | ||
| * } | ||
| * return {success: true}; | ||
| * }; | ||
| */ | ||
| validate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined; | ||
| } | ||
| /** | ||
| * Abstract class for loading records from a file. | ||
| * @template Options Options for the loader | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| * Abstract class for loading records from a file. | ||
| * @template Options Options for the loader | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare abstract class AbstractFileRecordLoader<Options extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>, OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends MapConfigLoader<Options, OverrideMap> { | ||
@@ -80,4 +53,4 @@ abstract readonly loaderType: Lowercase<string>; | ||
| /** | ||
| * If the loader is watching the file, it will stop watching. | ||
| */ | ||
| * If the loader is watching the file, it will stop watching. | ||
| */ | ||
| close(): Promise<void>; | ||
@@ -90,24 +63,38 @@ protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| /** | ||
| * Handle the parsing of the file. | ||
| */ | ||
| * Handle the parsing of the file. | ||
| */ | ||
| protected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>; | ||
| } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.d.ts | ||
| //#region src/DockerSecretsConfigLoader.d.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<'json'>, OverrideMap> { | ||
| * DockerSecretsConfigLoaderOptions is the interface for DockerSecretsConfigLoader options | ||
| * @category Loaders | ||
| * @since v1.0.0 | ||
| * @template OverrideMap - the type of the override key map | ||
| */ | ||
| interface DockerSecretsConfigLoaderOptions { | ||
| /** force file name to lower case */ | ||
| fileLowerCase: boolean; | ||
| /** path to docker secrets, default is '/run/secrets' */ | ||
| path: string; | ||
| /** set to false if need errors */ | ||
| isSilent: boolean; | ||
| /** optional logger */ | ||
| logger: ILoggerLike | undefined; | ||
| /** set to true to disable loader, default is false */ | ||
| disabled: boolean; | ||
| } | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<DockerSecretsConfigLoaderOptions, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<'json'>; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined>; | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| private convertObjectToStringRecord; | ||
| private valuePromises; | ||
| protected defaultOptions: DockerSecretsConfigLoaderOptions; | ||
| constructor(options: Loadable<Partial<DockerSecretsConfigLoaderOptions>>, overrideKeys?: Partial<OverrideMap>, loaderType?: Lowercase<string>); | ||
| protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| private filePath; | ||
| } | ||
@@ -117,14 +104,33 @@ //#endregion | ||
| /** | ||
| * Loader for dotenv files, using the `dotenv` packages parser. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| * Loader for dotenv files, using the `dotenv` packages parser. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<string>, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<'env'>; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<"env">; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<"env">>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer): Record<string, string | undefined>; | ||
| } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.d.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<"json">, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<"json">; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<"json">>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<"json">): Record<string, string | undefined>; | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| private convertObjectToStringRecord; | ||
| } | ||
| //#endregion | ||
| export { AbstractFileRecordLoader, AbstractFileRecordLoaderOptions, DockerSecretsConfigLoader, DockerSecretsConfigLoaderOptions, DotEnvLoader, FileConfigLoader }; | ||
| //# sourceMappingURL=index.d.cts.map |
+80
-74
| import { ConfigLoader, IConfigLoaderProps, LoaderValue, MapConfigLoader, OverrideKeyMap, ValidateCallback, VariableError } from "@avanio/variable-util"; | ||
| import { IResult } from "@luolapeikko/result-option"; | ||
| import { Loadable } from "@luolapeikko/ts-common"; | ||
| import { IResult } from "@luolapeikko/result-option"; | ||
| import { ILoggerLike } from "@avanio/logger-like"; | ||
| //#region src/DockerSecretsConfigLoader.d.ts | ||
| interface DockerSecretsConfigLoaderOptions { | ||
| /** force file name to lower case */ | ||
| fileLowerCase: boolean; | ||
| /** path to docker secrets, default is '/run/secrets' */ | ||
| path: string; | ||
| /** set to false if need errors */ | ||
| isSilent: boolean; | ||
| /** optional logger */ | ||
| logger: ILoggerLike | undefined; | ||
| /** set to true to disable loader, default is false */ | ||
| disabled: boolean; | ||
| } | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<DockerSecretsConfigLoaderOptions, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| private valuePromises; | ||
| protected defaultOptions: DockerSecretsConfigLoaderOptions; | ||
| constructor(options: Loadable<Partial<DockerSecretsConfigLoaderOptions>>, overrideKeys?: Partial<OverrideMap>, loaderType?: Lowercase<string>); | ||
| protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| private filePath; | ||
| } | ||
| //#endregion | ||
| //#region src/AbstractFileRecordLoader.d.ts | ||
| /** | ||
| * Options for the AbstractFileRecordLoader. | ||
| * @template FileType Type of the file | ||
| * @since v1.0.0 | ||
| */ | ||
| * Options for the AbstractFileRecordLoader. | ||
| * @template FileType Type of the file | ||
| * @since v1.0.0 | ||
| */ | ||
| interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps { | ||
@@ -52,22 +25,22 @@ fileType: FileType; | ||
| /** | ||
| * optional validator for data (Record<string, string | undefined>) | ||
| * @example | ||
| * // using zod | ||
| * const stringRecordSchema = z.record(z.string().min(1), z.string()); | ||
| * const validate: ValidateCallback<Record<string, string>> = async (data) => { | ||
| * const result = await stringRecordSchema.safeParseAsync(data); | ||
| * if (!result.success) { | ||
| * return {success: false, message: result.error.message}; | ||
| * } | ||
| * return {success: true}; | ||
| * }; | ||
| */ | ||
| * optional validator for data (Record<string, string | undefined>) | ||
| * @example | ||
| * // using zod | ||
| * const stringRecordSchema = z.record(z.string().min(1), z.string()); | ||
| * const validate: ValidateCallback<Record<string, string>> = async (data) => { | ||
| * const result = await stringRecordSchema.safeParseAsync(data); | ||
| * if (!result.success) { | ||
| * return {success: false, message: result.error.message}; | ||
| * } | ||
| * return {success: true}; | ||
| * }; | ||
| */ | ||
| validate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined; | ||
| } | ||
| /** | ||
| * Abstract class for loading records from a file. | ||
| * @template Options Options for the loader | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| * Abstract class for loading records from a file. | ||
| * @template Options Options for the loader | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare abstract class AbstractFileRecordLoader<Options extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>, OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends MapConfigLoader<Options, OverrideMap> { | ||
@@ -80,4 +53,4 @@ abstract readonly loaderType: Lowercase<string>; | ||
| /** | ||
| * If the loader is watching the file, it will stop watching. | ||
| */ | ||
| * If the loader is watching the file, it will stop watching. | ||
| */ | ||
| close(): Promise<void>; | ||
@@ -90,24 +63,38 @@ protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| /** | ||
| * Handle the parsing of the file. | ||
| */ | ||
| * Handle the parsing of the file. | ||
| */ | ||
| protected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>; | ||
| } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.d.ts | ||
| //#region src/DockerSecretsConfigLoader.d.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<'json'>, OverrideMap> { | ||
| * DockerSecretsConfigLoaderOptions is the interface for DockerSecretsConfigLoader options | ||
| * @category Loaders | ||
| * @since v1.0.0 | ||
| * @template OverrideMap - the type of the override key map | ||
| */ | ||
| interface DockerSecretsConfigLoaderOptions { | ||
| /** force file name to lower case */ | ||
| fileLowerCase: boolean; | ||
| /** path to docker secrets, default is '/run/secrets' */ | ||
| path: string; | ||
| /** set to false if need errors */ | ||
| isSilent: boolean; | ||
| /** optional logger */ | ||
| logger: ILoggerLike | undefined; | ||
| /** set to true to disable loader, default is false */ | ||
| disabled: boolean; | ||
| } | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<DockerSecretsConfigLoaderOptions, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<'json'>; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined>; | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| private convertObjectToStringRecord; | ||
| private valuePromises; | ||
| protected defaultOptions: DockerSecretsConfigLoaderOptions; | ||
| constructor(options: Loadable<Partial<DockerSecretsConfigLoaderOptions>>, overrideKeys?: Partial<OverrideMap>, loaderType?: Lowercase<string>); | ||
| protected handleLoaderValue(lookupKey: string): Promise<LoaderValue>; | ||
| private filePath; | ||
| } | ||
@@ -117,14 +104,33 @@ //#endregion | ||
| /** | ||
| * Loader for dotenv files, using the `dotenv` packages parser. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| * Loader for dotenv files, using the `dotenv` packages parser. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<string>, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<'env'>; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<"env">; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<"env">>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer): Record<string, string | undefined>; | ||
| } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.d.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| declare class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<AbstractFileRecordLoaderOptions<"json">, OverrideMap> { | ||
| readonly loaderType: Lowercase<string>; | ||
| protected defaultOptions: AbstractFileRecordLoaderOptions<"json">; | ||
| constructor(options: Loadable<Partial<AbstractFileRecordLoaderOptions<"json">>>, overrideKeys?: Partial<OverrideMap>, type?: Lowercase<string>); | ||
| protected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<"json">): Record<string, string | undefined>; | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| private convertObjectToStringRecord; | ||
| } | ||
| //#endregion | ||
| export { AbstractFileRecordLoader, AbstractFileRecordLoaderOptions, DockerSecretsConfigLoader, DockerSecretsConfigLoaderOptions, DotEnvLoader, FileConfigLoader }; | ||
| //# sourceMappingURL=index.d.mts.map |
+72
-72
@@ -0,51 +1,9 @@ | ||
| import { ConfigLoader, MapConfigLoader, VariableError, VariableLookupError, applyStringMap } from "@avanio/variable-util"; | ||
| import { Err, Ok } from "@luolapeikko/result-option"; | ||
| import { ErrorCore, UndefCore } from "@luolapeikko/ts-common"; | ||
| import { existsSync, watch } from "fs"; | ||
| import { readFile } from "fs/promises"; | ||
| import * as path from "path"; | ||
| import { ConfigLoader, MapConfigLoader, VariableError, VariableLookupError, applyStringMap } from "@avanio/variable-util"; | ||
| import { ErrorCore, UndefCore } from "@luolapeikko/ts-common"; | ||
| import { Err, Ok } from "@luolapeikko/result-option"; | ||
| import { parse } from "dotenv"; | ||
| //#region src/DockerSecretsConfigLoader.ts | ||
| /** | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var DockerSecretsConfigLoader = class extends ConfigLoader { | ||
| loaderType; | ||
| valuePromises = /* @__PURE__ */ new Map(); | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileLowerCase: false, | ||
| isSilent: true, | ||
| logger: void 0, | ||
| path: "/run/secrets" | ||
| }; | ||
| constructor(options, overrideKeys, loaderType = "docker-secrets") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = loaderType; | ||
| } | ||
| async handleLoaderValue(lookupKey) { | ||
| const options = await this.getOptions(); | ||
| const filePath = this.filePath(lookupKey, options); | ||
| let valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(void 0); | ||
| if (!this.valuePromises.has(lookupKey)) { | ||
| if (!existsSync(filePath)) { | ||
| if (!options.isSilent) throw new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| options.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| } else valuePromise = readFile(filePath, "utf8"); | ||
| this.valuePromises.set(lookupKey, valuePromise); | ||
| } | ||
| return { | ||
| path: filePath, | ||
| value: await valuePromise | ||
| }; | ||
| } | ||
| filePath(key, options) { | ||
| return path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/AbstractFileRecordLoader.ts | ||
@@ -82,4 +40,4 @@ /** | ||
| return { | ||
| value: this.data.get(lookupKey), | ||
| path: fileName | ||
| path: fileName, | ||
| value: this.data.get(lookupKey) | ||
| }; | ||
@@ -139,41 +97,40 @@ } | ||
| //#endregion | ||
| //#region src/FileConfigLoader.ts | ||
| //#region src/DockerSecretsConfigLoader.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * Loader for docker secrets, reads secrets from the `/run/secrets` directory. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var FileConfigLoader = class extends AbstractFileRecordLoader { | ||
| var DockerSecretsConfigLoader = class extends ConfigLoader { | ||
| loaderType; | ||
| valuePromises = /* @__PURE__ */ new Map(); | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileName: "config.json", | ||
| fileType: "json", | ||
| fileLowerCase: false, | ||
| isSilent: true, | ||
| logger: void 0, | ||
| validate: void 0, | ||
| watch: false | ||
| path: "/run/secrets" | ||
| }; | ||
| constructor(options, overrideKeys, type = "file") { | ||
| constructor(options, overrideKeys, loaderType = "docker-secrets") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = type; | ||
| this.loaderType = loaderType; | ||
| } | ||
| handleParse(rawData, options) { | ||
| const data = JSON.parse(rawData.toString()); | ||
| if (typeof data !== "object" || data === null || Array.isArray(data)) { | ||
| options.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`); | ||
| return {}; | ||
| async handleLoaderValue(lookupKey) { | ||
| const options = await this.getOptions(); | ||
| const filePath = this.filePath(lookupKey, options); | ||
| let valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(void 0); | ||
| if (!this.valuePromises.has(lookupKey)) { | ||
| if (!existsSync(filePath)) { | ||
| if (!options.isSilent) throw new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| options.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`); | ||
| } else valuePromise = readFile(filePath, "utf8"); | ||
| this.valuePromises.set(lookupKey, valuePromise); | ||
| } | ||
| return this.convertObjectToStringRecord(data); | ||
| return { | ||
| path: filePath, | ||
| value: await valuePromise | ||
| }; | ||
| } | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| convertObjectToStringRecord(data) { | ||
| return Object.entries(data).reduce((acc, [key, value]) => { | ||
| if (UndefCore.isNotNullish(value)) acc[key] = String(value); | ||
| return acc; | ||
| }, {}); | ||
| filePath(key, options) { | ||
| return path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key); | ||
| } | ||
@@ -210,3 +167,46 @@ }; | ||
| //#endregion | ||
| //#region src/FileConfigLoader.ts | ||
| /** | ||
| * A file-based configuration loader that reads a JSON file. | ||
| * @template OverrideMap Type of the override keys | ||
| * @since v1.0.0 | ||
| */ | ||
| var FileConfigLoader = class extends AbstractFileRecordLoader { | ||
| loaderType; | ||
| defaultOptions = { | ||
| disabled: false, | ||
| fileName: "config.json", | ||
| fileType: "json", | ||
| isSilent: true, | ||
| logger: void 0, | ||
| validate: void 0, | ||
| watch: false | ||
| }; | ||
| constructor(options, overrideKeys, type = "file") { | ||
| super(options, overrideKeys); | ||
| this.loaderType = type; | ||
| } | ||
| handleParse(rawData, options) { | ||
| const data = JSON.parse(rawData.toString()); | ||
| if (typeof data !== "object" || data === null || Array.isArray(data)) { | ||
| options.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`); | ||
| return {}; | ||
| } | ||
| return this.convertObjectToStringRecord(data); | ||
| } | ||
| /** | ||
| * Converts an object to a record of strings as env values are always strings. | ||
| * @param {object} data The object to convert | ||
| * @returns {Record<string, string>} The converted object | ||
| */ | ||
| convertObjectToStringRecord(data) { | ||
| return Object.entries(data).reduce((acc, [key, value]) => { | ||
| if (UndefCore.isNotNullish(value)) acc[key] = String(value); | ||
| return acc; | ||
| }, {}); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { AbstractFileRecordLoader, DockerSecretsConfigLoader, DotEnvLoader, FileConfigLoader }; | ||
| //# sourceMappingURL=index.mjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/DockerSecretsConfigLoader.ts","../src/AbstractFileRecordLoader.ts","../src/FileConfigLoader.ts","../src/DotEnvLoader.ts"],"sourcesContent":["import {existsSync} from 'fs';\nimport {readFile} from 'fs/promises';\nimport * as path from 'path';\nimport type {ILoggerLike} from '@avanio/logger-like';\nimport {ConfigLoader, type LoaderValue, type OverrideKeyMap, VariableLookupError} from '@avanio/variable-util';\nimport {type Loadable} from '@luolapeikko/ts-common';\n\nexport interface DockerSecretsConfigLoaderOptions {\n\t/** force file name to lower case */\n\tfileLowerCase: boolean;\n\t/** path to docker secrets, default is '/run/secrets' */\n\tpath: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to disable loader, default is false */\n\tdisabled: boolean;\n}\n\n/**\n * Loader for docker secrets, reads secrets from the `/run/secrets` directory.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<\n\tDockerSecretsConfigLoaderOptions,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\tprivate valuePromises = new Map<string, Promise<string | undefined>>();\n\tprotected defaultOptions: DockerSecretsConfigLoaderOptions = {\n\t\tdisabled: false,\n\t\tfileLowerCase: false,\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tpath: '/run/secrets',\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<DockerSecretsConfigLoaderOptions>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\tloaderType: Lowercase<string> = 'docker-secrets',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = loaderType;\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst options = await this.getOptions();\n\t\tconst filePath = this.filePath(lookupKey, options);\n\t\tlet valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(undefined);\n\t\tconst seen = this.valuePromises.has(lookupKey); // if valuePromise exists, it means we have seen this key before\n\t\tif (!seen) {\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tif (!options.isSilent) {\n\t\t\t\t\tthrow new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t\t}\n\t\t\t\toptions.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t} else {\n\t\t\t\tvaluePromise = readFile(filePath, 'utf8');\n\t\t\t}\n\t\t\t// store value promise as haven't seen this key before\n\t\t\tthis.valuePromises.set(lookupKey, valuePromise);\n\t\t}\n\t\treturn {path: filePath, value: await valuePromise};\n\t}\n\n\tprivate filePath(key: string, options: DockerSecretsConfigLoaderOptions): string {\n\t\treturn path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key);\n\t}\n}\n","import {existsSync, type FSWatcher, watch} from 'fs';\nimport {readFile} from 'fs/promises';\nimport type {ILoggerLike} from '@avanio/logger-like';\nimport {\n\tapplyStringMap,\n\ttype IConfigLoaderProps,\n\ttype LoaderValue,\n\tMapConfigLoader,\n\ttype OverrideKeyMap,\n\ttype ValidateCallback,\n\tVariableError,\n} from '@avanio/variable-util';\nimport {Err, type IResult, Ok} from '@luolapeikko/result-option';\nimport {ErrorCore, type Loadable} from '@luolapeikko/ts-common';\n\n/**\n * Options for the AbstractFileRecordLoader.\n * @template FileType Type of the file\n * @since v1.0.0\n */\nexport interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps {\n\tfileType: FileType;\n\t/** file name to load */\n\tfileName: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to watch file for changes */\n\twatch: boolean;\n\t/** set to true to disable loader */\n\tdisabled: boolean;\n\t/**\n\t * optional validator for data (Record<string, string | undefined>)\n\t * @example\n\t * // using zod\n\t * const stringRecordSchema = z.record(z.string().min(1), z.string());\n\t * const validate: ValidateCallback<Record<string, string>> = async (data) => {\n\t * const result = await stringRecordSchema.safeParseAsync(data);\n\t * if (!result.success) {\n\t * return {success: false, message: result.error.message};\n\t * }\n\t * return {success: true};\n\t * };\n\t */\n\tvalidate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined;\n}\n\n/**\n * Abstract class for loading records from a file.\n * @template Options Options for the loader\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport abstract class AbstractFileRecordLoader<\n\tOptions extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>,\n\tOverrideMap extends OverrideKeyMap = OverrideKeyMap,\n> extends MapConfigLoader<Options, OverrideMap> {\n\tabstract readonly loaderType: Lowercase<string>;\n\tprotected abstract defaultOptions: Options;\n\tprivate watcher: FSWatcher | undefined;\n\tprivate timeout: ReturnType<typeof setTimeout> | undefined;\n\n\tpublic constructor(options: Loadable<Partial<Options>>, overrideKeys?: Partial<OverrideMap>) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.handleFileChange = this.handleFileChange.bind(this);\n\t}\n\n\t/**\n\t * If the loader is watching the file, it will stop watching.\n\t */\n\tpublic async close(): Promise<void> {\n\t\tif (this.watcher) {\n\t\t\tconst {logger, fileName} = await this.getOptions();\n\t\t\tlogger?.debug(this.buildErrorStr(`closing file watcher for ${fileName}`));\n\t\t\tthis.watcher.close();\n\t\t}\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst {fileName} = await this.getOptions();\n\t\tif (!this._isLoaded) {\n\t\t\tawait this.loadData();\n\t\t\tthis._isLoaded = true; // only load data once to prevent spamming\n\t\t}\n\t\tconst value = this.data.get(lookupKey);\n\t\treturn {value, path: fileName};\n\t}\n\n\tprotected async handleData(): Promise<IResult<Record<string, string | undefined>, VariableError>> {\n\t\tconst options = await this.getOptions();\n\t\toptions.logger?.debug(this.buildErrorStr(`loading file ${options.fileName}`));\n\t\tif (!existsSync(options.fileName)) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} not found`)));\n\t\t}\n\t\tlet buffer;\n\t\ttry {\n\t\t\tbuffer = await readFile(options.fileName);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(ErrorCore.from(cause).message, {cause}));\n\t\t}\n\t\ttry {\n\t\t\tlet data = await this.handleParse(buffer, options);\n\t\t\tif (options.validate) {\n\t\t\t\tdata = await options.validate(data);\n\t\t\t}\n\t\t\tthis.handleFileWatch(options); // add watch after successful load\n\t\t\treturn Ok(data);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} is not a valid ${options.fileType}`), {cause}));\n\t\t}\n\t}\n\n\tprotected async handleLoadData(): Promise<boolean> {\n\t\tconst {logger, isSilent} = await this.getOptions();\n\t\tconst res = await this.handleData();\n\t\tif (res.isErr) {\n\t\t\tif (!isSilent) {\n\t\t\t\tres.unwrap();\n\t\t\t} else {\n\t\t\t\tlogger?.debug(res.err());\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tapplyStringMap(res.ok(), this.data);\n\t\treturn true;\n\t}\n\n\tprivate handleFileWatch(options: Options): void {\n\t\tif (options.watch && !this.watcher) {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`opening file watcher for ${options.fileName}`));\n\t\t\tthis.watcher = watch(options.fileName, () => {\n\t\t\t\tif (this.timeout) {\n\t\t\t\t\tclearTimeout(this.timeout);\n\t\t\t\t}\n\t\t\t\t// delay to prevent multiple reloads\n\t\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\t\tvoid this.handleFileChange(options);\n\t\t\t\t}, 200);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async handleFileChange(options: Options): Promise<void> {\n\t\ttry {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`file ${options.fileName} changed`));\n\t\t\tawait this.reload();\n\t\t} catch (err) {\n\t\t\toptions.logger?.error(this.buildErrorStr(`error reloading file ${options.fileName}: ${ErrorCore.from(err).message}`));\n\t\t}\n\t}\n\n\t/**\n\t * Handle the parsing of the file.\n\t */\n\tprotected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>;\n}\n","import {type OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable, UndefCore} from '@luolapeikko/ts-common';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * A file-based configuration loader that reads a JSON file.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<'json'>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'json'> = {\n\t\tdisabled: false,\n\t\tfileName: 'config.json',\n\t\tfileType: 'json',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'file',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined> {\n\t\tconst data: unknown = JSON.parse(rawData.toString());\n\t\tif (typeof data !== 'object' || data === null || Array.isArray(data)) {\n\t\t\toptions.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`);\n\t\t\treturn {};\n\t\t}\n\t\treturn this.convertObjectToStringRecord(data);\n\t}\n\n\t/**\n\t * Converts an object to a record of strings as env values are always strings.\n\t * @param {object} data The object to convert\n\t * @returns {Record<string, string>} The converted object\n\t */\n\tprivate convertObjectToStringRecord(data: object): Record<string, string> {\n\t\treturn Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {\n\t\t\tif (UndefCore.isNotNullish(value)) {\n\t\t\t\tacc[key] = String(value);\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {});\n\t}\n}\n","import {type OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable} from '@luolapeikko/ts-common';\nimport {parse} from 'dotenv';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * Loader for dotenv files, using the `dotenv` packages parser.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<string>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'env'> = {\n\t\tdisabled: false,\n\t\tfileName: '.env',\n\t\tfileType: 'env',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'dotenv',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer): Record<string, string | undefined> {\n\t\treturn parse(rawData);\n\t}\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"index.mjs","names":[],"sources":["../src/AbstractFileRecordLoader.ts","../src/DockerSecretsConfigLoader.ts","../src/DotEnvLoader.ts","../src/FileConfigLoader.ts"],"sourcesContent":["import type {ILoggerLike} from '@avanio/logger-like';\nimport {\n\tapplyStringMap,\n\ttype IConfigLoaderProps,\n\ttype LoaderValue,\n\tMapConfigLoader,\n\ttype OverrideKeyMap,\n\ttype ValidateCallback,\n\tVariableError,\n} from '@avanio/variable-util';\nimport {Err, type IResult, Ok} from '@luolapeikko/result-option';\nimport {ErrorCore, type Loadable} from '@luolapeikko/ts-common';\nimport {existsSync, type FSWatcher, watch} from 'fs';\nimport {readFile} from 'fs/promises';\n\n/**\n * Options for the AbstractFileRecordLoader.\n * @template FileType Type of the file\n * @since v1.0.0\n */\nexport interface AbstractFileRecordLoaderOptions<FileType extends string> extends IConfigLoaderProps {\n\tfileType: FileType;\n\t/** file name to load */\n\tfileName: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to watch file for changes */\n\twatch: boolean;\n\t/** set to true to disable loader */\n\tdisabled: boolean;\n\t/**\n\t * optional validator for data (Record<string, string | undefined>)\n\t * @example\n\t * // using zod\n\t * const stringRecordSchema = z.record(z.string().min(1), z.string());\n\t * const validate: ValidateCallback<Record<string, string>> = async (data) => {\n\t * const result = await stringRecordSchema.safeParseAsync(data);\n\t * if (!result.success) {\n\t * return {success: false, message: result.error.message};\n\t * }\n\t * return {success: true};\n\t * };\n\t */\n\tvalidate: ValidateCallback<Record<string, string | undefined>, Record<string, string | undefined>> | undefined;\n}\n\n/**\n * Abstract class for loading records from a file.\n * @template Options Options for the loader\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport abstract class AbstractFileRecordLoader<\n\tOptions extends AbstractFileRecordLoaderOptions<string> = AbstractFileRecordLoaderOptions<string>,\n\tOverrideMap extends OverrideKeyMap = OverrideKeyMap,\n> extends MapConfigLoader<Options, OverrideMap> {\n\tpublic abstract readonly loaderType: Lowercase<string>;\n\tprotected abstract defaultOptions: Options;\n\tprivate watcher: FSWatcher | undefined;\n\tprivate timeout: ReturnType<typeof setTimeout> | undefined;\n\n\tpublic constructor(options: Loadable<Partial<Options>>, overrideKeys?: Partial<OverrideMap>) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.handleFileChange = this.handleFileChange.bind(this);\n\t}\n\n\t/**\n\t * If the loader is watching the file, it will stop watching.\n\t */\n\tpublic async close(): Promise<void> {\n\t\tif (this.watcher) {\n\t\t\tconst {logger, fileName} = await this.getOptions();\n\t\t\tlogger?.debug(this.buildErrorStr(`closing file watcher for ${fileName}`));\n\t\t\tthis.watcher.close();\n\t\t}\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst {fileName} = await this.getOptions();\n\t\tif (!this._isLoaded) {\n\t\t\tawait this.loadData();\n\t\t\tthis._isLoaded = true; // only load data once to prevent spamming\n\t\t}\n\t\tconst value = this.data.get(lookupKey);\n\t\treturn {path: fileName, value};\n\t}\n\n\tprotected async handleData(): Promise<IResult<Record<string, string | undefined>, VariableError>> {\n\t\tconst options = await this.getOptions();\n\t\toptions.logger?.debug(this.buildErrorStr(`loading file ${options.fileName}`));\n\t\tif (!existsSync(options.fileName)) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} not found`)));\n\t\t}\n\t\tlet buffer: Buffer;\n\t\ttry {\n\t\t\tbuffer = await readFile(options.fileName);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(ErrorCore.from(cause).message, {cause}));\n\t\t}\n\t\ttry {\n\t\t\tlet data = await this.handleParse(buffer, options);\n\t\t\tif (options.validate) {\n\t\t\t\tdata = await options.validate(data);\n\t\t\t}\n\t\t\tthis.handleFileWatch(options); // add watch after successful load\n\t\t\treturn Ok(data);\n\t\t} catch (cause) {\n\t\t\treturn Err(new VariableError(this.buildErrorStr(`file ${options.fileName} is not a valid ${options.fileType}`), {cause}));\n\t\t}\n\t}\n\n\tprotected async handleLoadData(): Promise<boolean> {\n\t\tconst {logger, isSilent} = await this.getOptions();\n\t\tconst res = await this.handleData();\n\t\tif (res.isErr) {\n\t\t\tif (!isSilent) {\n\t\t\t\tres.unwrap();\n\t\t\t} else {\n\t\t\t\tlogger?.debug(res.err());\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\tapplyStringMap(res.ok(), this.data);\n\t\treturn true;\n\t}\n\n\tprivate handleFileWatch(options: Options): void {\n\t\tif (options.watch && !this.watcher) {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`opening file watcher for ${options.fileName}`));\n\t\t\tthis.watcher = watch(options.fileName, () => {\n\t\t\t\tif (this.timeout) {\n\t\t\t\t\tclearTimeout(this.timeout);\n\t\t\t\t}\n\t\t\t\t// delay to prevent multiple reloads\n\t\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\t\tvoid this.handleFileChange(options);\n\t\t\t\t}, 200);\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async handleFileChange(options: Options): Promise<void> {\n\t\ttry {\n\t\t\toptions.logger?.debug(this.buildErrorStr(`file ${options.fileName} changed`));\n\t\t\tawait this.reload();\n\t\t} catch (err) {\n\t\t\toptions.logger?.error(this.buildErrorStr(`error reloading file ${options.fileName}: ${ErrorCore.from(err).message}`));\n\t\t}\n\t}\n\n\t/**\n\t * Handle the parsing of the file.\n\t */\n\tprotected abstract handleParse(rawData: Buffer, options: Options): Record<string, string | undefined> | Promise<Record<string, string | undefined>>;\n}\n","import type {ILoggerLike} from '@avanio/logger-like';\nimport {ConfigLoader, type LoaderValue, type OverrideKeyMap, VariableLookupError} from '@avanio/variable-util';\nimport type {Loadable} from '@luolapeikko/ts-common';\nimport {existsSync} from 'fs';\nimport {readFile} from 'fs/promises';\nimport * as path from 'path';\n\n/**\n * DockerSecretsConfigLoaderOptions is the interface for DockerSecretsConfigLoader options\n * @category Loaders\n * @since v1.0.0\n * @template OverrideMap - the type of the override key map\n */\nexport interface DockerSecretsConfigLoaderOptions {\n\t/** force file name to lower case */\n\tfileLowerCase: boolean;\n\t/** path to docker secrets, default is '/run/secrets' */\n\tpath: string;\n\t/** set to false if need errors */\n\tisSilent: boolean;\n\t/** optional logger */\n\tlogger: ILoggerLike | undefined;\n\t/** set to true to disable loader, default is false */\n\tdisabled: boolean;\n}\n\n/**\n * Loader for docker secrets, reads secrets from the `/run/secrets` directory.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DockerSecretsConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends ConfigLoader<\n\tDockerSecretsConfigLoaderOptions,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\tprivate valuePromises = new Map<string, Promise<string | undefined>>();\n\tprotected defaultOptions: DockerSecretsConfigLoaderOptions = {\n\t\tdisabled: false,\n\t\tfileLowerCase: false,\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tpath: '/run/secrets',\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<DockerSecretsConfigLoaderOptions>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\tloaderType: Lowercase<string> = 'docker-secrets',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = loaderType;\n\t}\n\n\tprotected async handleLoaderValue(lookupKey: string): Promise<LoaderValue> {\n\t\tconst options = await this.getOptions();\n\t\tconst filePath = this.filePath(lookupKey, options);\n\t\tlet valuePromise = this.valuePromises.get(lookupKey) ?? Promise.resolve(undefined);\n\t\tconst seen = this.valuePromises.has(lookupKey); // if valuePromise exists, it means we have seen this key before\n\t\tif (!seen) {\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tif (!options.isSilent) {\n\t\t\t\t\tthrow new VariableLookupError(lookupKey, `ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t\t}\n\t\t\t\toptions.logger?.debug(`ConfigVariables[${this.loaderType}]: ${lookupKey} from ${filePath} not found`);\n\t\t\t} else {\n\t\t\t\tvaluePromise = readFile(filePath, 'utf8');\n\t\t\t}\n\t\t\t// store value promise as haven't seen this key before\n\t\t\tthis.valuePromises.set(lookupKey, valuePromise);\n\t\t}\n\t\treturn {path: filePath, value: await valuePromise};\n\t}\n\n\tprivate filePath(key: string, options: DockerSecretsConfigLoaderOptions): string {\n\t\treturn path.join(path.resolve(options.path), options.fileLowerCase ? key.toLowerCase() : key);\n\t}\n}\n","import type {OverrideKeyMap} from '@avanio/variable-util';\nimport type {Loadable} from '@luolapeikko/ts-common';\nimport {parse} from 'dotenv';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * Loader for dotenv files, using the `dotenv` packages parser.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class DotEnvLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<string>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'env'> = {\n\t\tdisabled: false,\n\t\tfileName: '.env',\n\t\tfileType: 'env',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'env'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'dotenv',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer): Record<string, string | undefined> {\n\t\treturn parse(rawData);\n\t}\n}\n","import type {OverrideKeyMap} from '@avanio/variable-util';\nimport {type Loadable, UndefCore} from '@luolapeikko/ts-common';\nimport {AbstractFileRecordLoader, type AbstractFileRecordLoaderOptions} from './AbstractFileRecordLoader';\n\n/**\n * A file-based configuration loader that reads a JSON file.\n * @template OverrideMap Type of the override keys\n * @since v1.0.0\n */\nexport class FileConfigLoader<OverrideMap extends OverrideKeyMap = OverrideKeyMap> extends AbstractFileRecordLoader<\n\tAbstractFileRecordLoaderOptions<'json'>,\n\tOverrideMap\n> {\n\tpublic readonly loaderType: Lowercase<string>;\n\n\tprotected defaultOptions: AbstractFileRecordLoaderOptions<'json'> = {\n\t\tdisabled: false,\n\t\tfileName: 'config.json',\n\t\tfileType: 'json',\n\t\tisSilent: true,\n\t\tlogger: undefined,\n\t\tvalidate: undefined,\n\t\twatch: false,\n\t};\n\n\tpublic constructor(\n\t\toptions: Loadable<Partial<AbstractFileRecordLoaderOptions<'json'>>>,\n\t\toverrideKeys?: Partial<OverrideMap>,\n\t\ttype: Lowercase<string> = 'file',\n\t) {\n\t\tsuper(options, overrideKeys);\n\t\tthis.loaderType = type;\n\t}\n\n\tprotected handleParse(rawData: Buffer, options: AbstractFileRecordLoaderOptions<'json'>): Record<string, string | undefined> {\n\t\tconst data: unknown = JSON.parse(rawData.toString());\n\t\tif (typeof data !== 'object' || data === null || Array.isArray(data)) {\n\t\t\toptions.logger?.error(`ConfigVariables[${this.loaderType}]: Invalid JSON data from ${options.fileName}`);\n\t\t\treturn {};\n\t\t}\n\t\treturn this.convertObjectToStringRecord(data);\n\t}\n\n\t/**\n\t * Converts an object to a record of strings as env values are always strings.\n\t * @param {object} data The object to convert\n\t * @returns {Record<string, string>} The converted object\n\t */\n\tprivate convertObjectToStringRecord(data: object): Record<string, string> {\n\t\treturn Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {\n\t\t\tif (UndefCore.isNotNullish(value)) {\n\t\t\t\tacc[key] = String(value);\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {});\n\t}\n}\n"],"mappings":"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"} |
+13
-48
| { | ||
| "name": "@avanio/variable-util-node", | ||
| "version": "1.2.2", | ||
| "version": "1.3.0", | ||
| "description": "nodejs env util", | ||
@@ -9,21 +9,16 @@ "main": "./dist/index.cjs", | ||
| "exports": { | ||
| "types": { | ||
| "import": "./dist/index.d.mts", | ||
| "require": "./dist/index.d.cts" | ||
| "import": { | ||
| "types": "./dist/index.d.mts", | ||
| "default": "./dist/index.mjs" | ||
| }, | ||
| "import": "./dist/index.mjs", | ||
| "require": "./dist/index.cjs", | ||
| "require": { | ||
| "types": "./dist/index.d.cts", | ||
| "default": "./dist/index.cjs" | ||
| }, | ||
| "default": "./dist/index.mjs" | ||
| }, | ||
| "private": false, | ||
| "engines": { | ||
| "node": ">=20" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsdown src/index.ts --sourcemap --format cjs,esm --dts --clean", | ||
| "prepublishOnly": "npm run build", | ||
| "test": "vitest test --run --no-isolate --coverage", | ||
| "coverage": "vitest test --run --no-isolate --reporter=dot --coverage --coverage.reporter=lcov", | ||
| "lint": "eslint . --ext .ts", | ||
| "validate": "tsc --noEmit --project tsconfig.test.json" | ||
| "build": "tsdown src/index.ts --sourcemap --format cjs,esm --dts --clean -c tsconfig.build.json", | ||
| "validate": "tsc --noEmit" | ||
| }, | ||
@@ -37,35 +32,6 @@ "files": [ | ||
| "devDependencies": { | ||
| "@avanio/logger-like": "^0.2.12", | ||
| "@avanio/variable-util": "^1.2.2", | ||
| "@cspell/eslint-plugin": "^9.2.1", | ||
| "@eslint/js": "^9.35.0", | ||
| "@luolapeikko/result-option": "^2.0.1", | ||
| "@luolapeikko/ts-common": "^1.1.1", | ||
| "@stylistic/eslint-plugin": "^5.3.1", | ||
| "@tsconfig/node20": "^20.1.6", | ||
| "@types/node": "^22.18.3", | ||
| "@types/sinon": "^17.0.4", | ||
| "@typescript-eslint/eslint-plugin": "^8.43.0", | ||
| "@typescript-eslint/parser": "^8.43.0", | ||
| "@vitest/coverage-v8": "^3.2.4", | ||
| "c8": "^10.1.3", | ||
| "dotenv": "^17.2.2", | ||
| "eslint": "^9.35.0", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-import-resolver-typescript": "^4.4.4", | ||
| "eslint-plugin-import": "^2.32.0", | ||
| "eslint-plugin-jsdoc": "^56.1.2", | ||
| "eslint-plugin-prettier": "^5.5.4", | ||
| "eslint-plugin-sonarjs": "^3.0.5", | ||
| "prettier": "^3.6.2", | ||
| "sinon": "^21.0.0", | ||
| "source-map-support": "^0.5.21", | ||
| "tsdown": "^0.20.3", | ||
| "typescript": "^5.9.2", | ||
| "typescript-eslint": "^8.43.0", | ||
| "vite": "^7.1.5", | ||
| "vitest": "^3.2.4" | ||
| "@avanio/variable-util": "workspace:*" | ||
| }, | ||
| "peerDependencies": { | ||
| "@avanio/logger-like": ">= 0.1.0", | ||
| "@avanio/logger-like": ">= 0.0.1", | ||
| "@avanio/variable-util": ">= 1.2.1", | ||
@@ -75,4 +41,3 @@ "@luolapeikko/result-option": ">= 1.0.0", | ||
| "dotenv": ">= 16.0.0" | ||
| }, | ||
| "packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67" | ||
| } | ||
| } |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
64020
5.54%1
-96.67%10
25%0
-100%