@maroonedsoftware/appconfig 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/app.config.d.ts +27 -5
- package/dist/app.config.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/app.config.provider.dotenv.d.ts.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -28,8 +28,9 @@ const config = new AppConfig({
|
|
|
28
28
|
api: { timeout: 5000 },
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const host = config.get('database')
|
|
32
|
-
const port = config.getNumber('port');
|
|
31
|
+
const host = config.get('database'); // Type-safe access
|
|
32
|
+
const port = config.getNumber('port'); // Returns as number
|
|
33
|
+
const db = config.getAs<{ host: string }>('database'); // Cast to interface
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
### Using the Builder
|
|
@@ -130,13 +131,14 @@ const config = await new AppConfigBuilder()
|
|
|
130
131
|
|
|
131
132
|
The configuration container providing type-safe access to configuration values.
|
|
132
133
|
|
|
133
|
-
| Method
|
|
134
|
-
|
|
|
135
|
-
| `get(key)`
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
134
|
+
| Method | Description |
|
|
135
|
+
| ------------------ | --------------------------------------------------- |
|
|
136
|
+
| `get(key)` | Returns the value for the key with full type safety |
|
|
137
|
+
| `getAs<U>(key)` | Returns the value cast to the specified type `U` |
|
|
138
|
+
| `getString(key)` | Returns the value converted to a string |
|
|
139
|
+
| `getNumber(key)` | Returns the value converted to a number |
|
|
140
|
+
| `getBoolean(key)` | Returns the value converted to a boolean |
|
|
141
|
+
| `getObject(key)` | Returns the value cast as an object |
|
|
140
142
|
|
|
141
143
|
### AppConfigBuilder
|
|
142
144
|
|
package/dist/app.config.d.ts
CHANGED
|
@@ -34,7 +34,29 @@ export declare class AppConfig<T = Record<string, unknown>> {
|
|
|
34
34
|
* const port = config.get('port'); // Returns 3000, typed as number
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
-
get
|
|
37
|
+
get(key: keyof T): T[keyof T];
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves a configuration value cast to a specific type.
|
|
40
|
+
*
|
|
41
|
+
* Unlike `get()`, which returns `T[keyof T]`, this method lets you cast the
|
|
42
|
+
* value to an arbitrary type `U`. Use this when the TypeScript type of the
|
|
43
|
+
* stored value differs from what you need at the call site — for example,
|
|
44
|
+
* when reading a nested object as a typed interface.
|
|
45
|
+
*
|
|
46
|
+
* @template U - The type to cast the value to.
|
|
47
|
+
* @param key - The configuration key to retrieve.
|
|
48
|
+
* @returns The configuration value cast to `U`.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* interface DbConfig { host: string; port: number }
|
|
53
|
+
*
|
|
54
|
+
* const config = new AppConfig({ database: { host: 'localhost', port: 5432 } });
|
|
55
|
+
* const db = config.getAs<DbConfig>('database');
|
|
56
|
+
* console.log(db.host); // 'localhost'
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
getAs<U>(key: keyof T): U;
|
|
38
60
|
/**
|
|
39
61
|
* Retrieves a configuration value as a string.
|
|
40
62
|
*
|
|
@@ -52,7 +74,7 @@ export declare class AppConfig<T = Record<string, unknown>> {
|
|
|
52
74
|
* const enabledStr = config.getString('enabled'); // Returns "true"
|
|
53
75
|
* ```
|
|
54
76
|
*/
|
|
55
|
-
getString
|
|
77
|
+
getString(key: keyof T): string;
|
|
56
78
|
/**
|
|
57
79
|
* Retrieves a configuration value as a number.
|
|
58
80
|
*
|
|
@@ -71,7 +93,7 @@ export declare class AppConfig<T = Record<string, unknown>> {
|
|
|
71
93
|
* const timeout = config.getNumber('timeout'); // Returns 5000
|
|
72
94
|
* ```
|
|
73
95
|
*/
|
|
74
|
-
getNumber
|
|
96
|
+
getNumber(key: keyof T): number;
|
|
75
97
|
/**
|
|
76
98
|
* Retrieves a configuration value as a boolean.
|
|
77
99
|
*
|
|
@@ -90,7 +112,7 @@ export declare class AppConfig<T = Record<string, unknown>> {
|
|
|
90
112
|
* const debug = config.getBoolean('debug'); // Returns true
|
|
91
113
|
* ```
|
|
92
114
|
*/
|
|
93
|
-
getBoolean
|
|
115
|
+
getBoolean(key: keyof T): boolean;
|
|
94
116
|
/**
|
|
95
117
|
* Retrieves a configuration value as an object.
|
|
96
118
|
*
|
|
@@ -109,6 +131,6 @@ export declare class AppConfig<T = Record<string, unknown>> {
|
|
|
109
131
|
* const db = config.getObject('database'); // Returns { host: 'localhost', port: 5432 }
|
|
110
132
|
* ```
|
|
111
133
|
*/
|
|
112
|
-
getObject
|
|
134
|
+
getObject(key: keyof T): object;
|
|
113
135
|
}
|
|
114
136
|
//# sourceMappingURL=app.config.d.ts.map
|
package/dist/app.config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.config.d.ts","sourceRoot":"","sources":["../src/app.config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,qBAAa,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAMpC,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC;;;;OAIG;gBAC0B,MAAM,EAAE,CAAC;IAEtC;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"app.config.d.ts","sourceRoot":"","sources":["../src/app.config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,qBAAa,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAMpC,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC;;;;OAIG;gBAC0B,MAAM,EAAE,CAAC;IAEtC;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAI7B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;IAIzB;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM;IAI/B;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM;IAI/B;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,OAAO;IAIjC;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,MAAM;CAGhC"}
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,30 @@ var AppConfig = class {
|
|
|
32
32
|
return this.config[key];
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
+
* Retrieves a configuration value cast to a specific type.
|
|
36
|
+
*
|
|
37
|
+
* Unlike `get()`, which returns `T[keyof T]`, this method lets you cast the
|
|
38
|
+
* value to an arbitrary type `U`. Use this when the TypeScript type of the
|
|
39
|
+
* stored value differs from what you need at the call site — for example,
|
|
40
|
+
* when reading a nested object as a typed interface.
|
|
41
|
+
*
|
|
42
|
+
* @template U - The type to cast the value to.
|
|
43
|
+
* @param key - The configuration key to retrieve.
|
|
44
|
+
* @returns The configuration value cast to `U`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* interface DbConfig { host: string; port: number }
|
|
49
|
+
*
|
|
50
|
+
* const config = new AppConfig({ database: { host: 'localhost', port: 5432 } });
|
|
51
|
+
* const db = config.getAs<DbConfig>('database');
|
|
52
|
+
* console.log(db.host); // 'localhost'
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
getAs(key) {
|
|
56
|
+
return this.config[key];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
35
59
|
* Retrieves a configuration value as a string.
|
|
36
60
|
*
|
|
37
61
|
* The value is converted to a string using `String()`. This is useful when
|
|
@@ -49,7 +73,7 @@ var AppConfig = class {
|
|
|
49
73
|
* ```
|
|
50
74
|
*/
|
|
51
75
|
getString(key) {
|
|
52
|
-
return String(this.
|
|
76
|
+
return String(this.get(key));
|
|
53
77
|
}
|
|
54
78
|
/**
|
|
55
79
|
* Retrieves a configuration value as a number.
|
|
@@ -409,6 +433,7 @@ var AppConfigProviderDotenv = class {
|
|
|
409
433
|
* @returns `true` if the value matches the provider's regex pattern, `false` otherwise.
|
|
410
434
|
*/
|
|
411
435
|
canParse(value) {
|
|
436
|
+
this.prefix.lastIndex = 0;
|
|
412
437
|
return this.prefix.test(value);
|
|
413
438
|
}
|
|
414
439
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app.config.ts","../src/app.config.builder.ts","../src/object.visitor.ts","../src/sources/app.config.source.dotenv.ts","../src/sources/app.config.source.json.ts","../src/sources/app.config.source.yaml.ts","../src/helpers.ts","../src/providers/app.config.provider.dotenv.ts","../src/providers/app.config.provider.gcp.secrets.ts"],"sourcesContent":["/**\n * Configuration container that provides type-safe access to configuration values.\n *\n * @template T - The type of the configuration object. Defaults to `Record<string, unknown>`.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({\n * database: { host: 'localhost', port: 5432 },\n * api: { timeout: 5000 }\n * });\n *\n * const host = config.get('database').host; // Type-safe access\n * ```\n */\nexport class AppConfig<T = Record<string, unknown>> {\n /**\n * Creates a new AppConfig instance with the provided configuration.\n *\n * @param config - The configuration object to wrap.\n */\n constructor(private readonly config: T) {}\n\n /**\n * Retrieves a configuration value by key.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value for the given key.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: 3000, host: 'localhost' });\n * const port = config.get('port'); // Returns 3000, typed as number\n * ```\n */\n get<K extends keyof T>(key: K): T[K] {\n return this.config[key];\n }\n\n /**\n * Retrieves a configuration value as a string.\n *\n * The value is converted to a string using `String()`. This is useful when\n * you need to ensure a value is a string regardless of its original type.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a string.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: 3000, enabled: true });\n * const portStr = config.getString('port'); // Returns \"3000\"\n * const enabledStr = config.getString('enabled'); // Returns \"true\"\n * ```\n */\n getString<K extends keyof T>(key: K): string {\n return String(this.config[key]);\n }\n\n /**\n * Retrieves a configuration value as a number.\n *\n * The value is converted to a number using `Number()`. This is useful when\n * you need to ensure a value is a number regardless of its original type.\n * Note: Invalid conversions will result in `NaN`.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a number.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: '3000', timeout: '5000' });\n * const port = config.getNumber('port'); // Returns 3000\n * const timeout = config.getNumber('timeout'); // Returns 5000\n * ```\n */\n getNumber<K extends keyof T>(key: K): number {\n return Number(this.config[key]);\n }\n\n /**\n * Retrieves a configuration value as a boolean.\n *\n * The value is converted to a boolean using `Boolean()`. This is useful when\n * you need to ensure a value is a boolean regardless of its original type.\n * Note: Only falsy values (false, 0, '', null, undefined, NaN) become false.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a boolean.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ enabled: 'true', debug: 1 });\n * const enabled = config.getBoolean('enabled'); // Returns true\n * const debug = config.getBoolean('debug'); // Returns true\n * ```\n */\n getBoolean<K extends keyof T>(key: K): boolean {\n return Boolean(this.config[key]);\n }\n\n /**\n * Retrieves a configuration value as an object.\n *\n * The value is cast to an object type. This is useful when you know a value\n * is an object and want to access it with object methods.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value cast as an object.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({\n * database: { host: 'localhost', port: 5432 }\n * });\n * const db = config.getObject('database'); // Returns { host: 'localhost', port: 5432 }\n * ```\n */\n getObject<K extends keyof T>(key: K): object {\n return this.config[key] as object;\n }\n}\n","import { deepmerge } from 'deepmerge-ts';\nimport { AppConfig } from './app.config.js';\nimport { AppConfigProvider } from './app.config.provider.js';\nimport { AppConfigSource } from './app.config.source.js';\nimport { objectVisitor, ObjectVisitorMeta } from './object.visitor.js';\n\n/**\n * Builder for constructing AppConfig instances from multiple sources with value transformation.\n *\n * The builder allows you to:\n * - Load configuration from multiple sources (files, environment variables, etc.)\n * - Merge configurations with later sources overriding earlier ones\n * - Transform string values using providers (e.g., resolving environment variable references)\n *\n * @example\n * ```typescript\n * const config = await new AppConfigBuilder()\n * .addSource(new AppConfigSourceJson('./config.json'))\n * .addSource(new AppConfigSourceDotenv())\n * .addProvider(new AppConfigProviderDotenv())\n * .build();\n * ```\n */\nexport class AppConfigBuilder {\n private readonly sources: AppConfigSource[] = [];\n private readonly providers: AppConfigProvider[] = [];\n\n /**\n * Adds a configuration source to the builder.\n *\n * Sources are loaded in the order they are added, and later sources override earlier ones\n * when merging configurations.\n *\n * @param source - The configuration source to add.\n * @returns The builder instance for method chaining.\n *\n * @example\n * ```typescript\n * builder\n * .addSource(new AppConfigSourceJson('./default.json'))\n * .addSource(new AppConfigSourceJson('./local.json'));\n * ```\n */\n addSource(source: AppConfigSource) {\n this.sources.push(source);\n return this;\n }\n\n /**\n * Adds a provider to transform string values during configuration building.\n *\n * Providers are applied to all string values found in the merged configuration.\n * The first provider that can parse a value will be used to transform it.\n *\n * @param provider - The provider to add.\n * @returns The builder instance for method chaining.\n *\n * @example\n * ```typescript\n * builder.addProvider(new AppConfigProviderDotenv());\n * ```\n */\n addProvider(provider: AppConfigProvider) {\n this.providers.push(provider);\n return this;\n }\n\n /**\n * Builds the AppConfig instance by loading all sources, merging them, and applying providers.\n *\n * The build process:\n * 1. Loads all sources in parallel\n * 2. Merges configurations (later sources override earlier ones)\n * 3. Traverses the merged configuration and applies providers to string values\n * 4. Returns the final AppConfig instance\n *\n * @template T - The type of the configuration object. Defaults to `Record<string, unknown>`.\n * @returns A promise that resolves to the built AppConfig instance.\n *\n * @example\n * ```typescript\n * const config = await builder.build<MyConfigType>();\n * const value = config.get('someKey');\n * ```\n */\n async build<T = Record<string, unknown>>(): Promise<AppConfig<T>> {\n const sourceTasks = await Promise.all(this.sources.map(x => x.load()));\n const mergedConfig = deepmerge(...sourceTasks) as T;\n\n const tasks: Promise<void>[] = [];\n const parse = (value: unknown, meta: ObjectVisitorMeta) => {\n if (typeof value === 'string') {\n const provider = this.providers.find(x => x.canParse(value));\n if (provider) {\n tasks.push(provider.parse(value, meta));\n }\n }\n };\n\n objectVisitor(mergedConfig, parse);\n await Promise.all(tasks);\n\n return new AppConfig<T>(mergedConfig);\n }\n}\n","/**\n * Metadata about a value's location within an object structure.\n */\nexport type ObjectVisitorMeta = {\n /** The full path to the value (e.g., \"database.host\" or \"items[0]\"). */\n path: string;\n /** The object that owns this property. */\n owner: object;\n /** The property name or array index path (e.g., \"host\" or \"items[0]\"). */\n propertyPath: string;\n /** The type of the property value. */\n propertyType: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';\n /** The array index if the value is in an array, undefined otherwise. */\n arrayIndex?: number;\n};\n\n/**\n * Callback function invoked for each primitive value found during object traversal.\n *\n * @param value - The primitive value found.\n * @param meta - Metadata about the value's location in the object structure.\n */\nexport type ObjectVisitorCallback = (value: unknown, meta: ObjectVisitorMeta) => void;\n\n/**\n * Traverses an object structure and invokes a callback for each primitive value found.\n *\n * The visitor recursively traverses objects and arrays, calling the callback for each\n * primitive value (string, number, boolean, bigint) encountered. It skips functions,\n * symbols, null, and undefined values.\n *\n * @param obj - The object to traverse. Can be any value.\n * @param callback - The callback function to invoke for each primitive value.\n *\n * @example\n * ```typescript\n * const config = {\n * database: { host: 'localhost', port: 5432 },\n * items: ['a', 'b', 'c']\n * };\n *\n * objectVisitor(config, (value, meta) => {\n * console.log(`${meta.path} = ${value}`);\n * });\n * // Output:\n * // database.host = localhost\n * // database.port = 5432\n * // items[0] = a\n * // items[1] = b\n * // items[2] = c\n * ```\n */\nexport const objectVisitor = (obj: unknown, callback: ObjectVisitorCallback): void => {\n const visit = (\n obj: unknown,\n callback: ObjectVisitorCallback,\n path: string = '',\n owner: object = {},\n propertyPath: string = '',\n arrayIndex?: number,\n ): void => {\n if (!obj) {\n return;\n }\n\n switch (typeof obj) {\n case 'object':\n if (Array.isArray(obj)) {\n obj.forEach((item, index) => {\n visit(item, callback, path + `[${index}]`, obj, propertyPath + `[${index}]`, index);\n });\n } else {\n const entries = Object.entries(obj);\n for (const entry of entries) {\n visit(entry[1], callback, path + (path.length > 0 ? '.' : '') + entry[0], obj, entry[0]);\n }\n }\n break;\n case 'function':\n case 'symbol':\n case 'undefined':\n break;\n default:\n callback(obj, {\n owner,\n propertyPath,\n path,\n propertyType: typeof obj,\n arrayIndex,\n });\n break;\n }\n };\n\n visit(obj, callback);\n};\n","import { AppConfigSource } from '../app.config.source.js';\nimport dotenv from 'dotenv';\n\n/**\n * Configuration source that loads environment variables from a `.env` file.\n *\n * This source uses the `dotenv` package to load environment variables from a `.env` file.\n * If no file path is provided, it will look for a `.env` file in the current working directory.\n * All values are strings as provided by the environment file.\n *\n * @example\n * ```typescript\n * // Load from default .env file\n * const source1 = new AppConfigSourceDotenv();\n * const config1 = await source1.load();\n *\n * // Load from custom path\n * const source2 = new AppConfigSourceDotenv('./config/.env.local');\n * const config2 = await source2.load();\n * ```\n */\nexport class AppConfigSourceDotenv implements AppConfigSource {\n /**\n * Creates a new AppConfigSourceDotenv instance.\n *\n * @param filePath - Optional path to the `.env` file. If not provided, `dotenv` will\n * look for a `.env` file in the current working directory.\n */\n constructor(private readonly filePath?: string) {}\n\n /**\n * Loads environment variables from the `.env` file.\n *\n * Uses `dotenv.config()` to parse the file and load variables into the returned object.\n * If the file doesn't exist or there's an error, an error will be thrown.\n *\n * @returns A promise that resolves to an object containing the parsed environment variables.\n * @throws {Error} If there's an error reading or parsing the `.env` file.\n */\n async load(): Promise<Record<string, unknown>> {\n const result = dotenv.config({ path: this.filePath, quiet: true });\n if (result.error) {\n throw result.error;\n }\n return Promise.resolve(result.parsed ?? {});\n }\n}\n","import { existsSync } from 'node:fs';\nimport { AppConfigSource } from '../app.config.source.js';\nimport { readFile } from 'node:fs/promises';\nimport { AppConfigSourceFileOptions } from '../app.config.source.options.js';\n\n/**\n * Configuration source that loads configuration from a JSON file.\n *\n * This source reads a JSON file from the filesystem and parses it as a configuration object.\n * By default, it will return an empty object if the file doesn't exist instead of throwing an error.\n *\n * @example\n * ```typescript\n * // Load from JSON file, ignore if missing\n * const source1 = new AppConfigSourceJson('./config.json');\n *\n * // Load from JSON file, throw error if missing\n * const source2 = new AppConfigSourceJson('./config.json', {\n * ignoreMissingFile: false\n * });\n *\n * // Load with custom encoding\n * const source3 = new AppConfigSourceJson('./config.json', {\n * encoding: 'utf16le'\n * });\n * ```\n */\nexport class AppConfigSourceJson implements AppConfigSource {\n private readonly options: AppConfigSourceFileOptions;\n\n /**\n * Creates a new AppConfigSourceJson instance.\n *\n * @param filePath - The path to the JSON file to load.\n * @param options - Optional configuration for the source behavior.\n */\n constructor(\n private readonly filePath: string,\n options?: AppConfigSourceFileOptions,\n ) {\n this.options = {\n ignoreMissingFile: true,\n encoding: 'utf8',\n ...(options ?? {}),\n };\n }\n\n /**\n * Loads configuration from the JSON file.\n *\n * If the file doesn't exist and `ignoreMissingFile` is `true`, returns an empty object.\n * Otherwise, reads and parses the JSON file.\n *\n * @returns A promise that resolves to the parsed JSON configuration object.\n * @throws {Error} If the file doesn't exist and `ignoreMissingFile` is `false`,\n * or if the file contains invalid JSON.\n */\n async load(): Promise<Record<string, unknown>> {\n if (!existsSync(this.filePath) && this.options.ignoreMissingFile) {\n return {};\n }\n\n const file = await readFile(this.filePath, {\n encoding: this.options.encoding,\n });\n return JSON.parse(file.toString());\n }\n}\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport YAML from 'yaml';\nimport { AppConfigSource } from '../app.config.source.js';\nimport { AppConfigSourceFileOptions } from '../app.config.source.options.js';\n\n/**\n * Configuration source that loads configuration from a YAML file.\n *\n * This source reads a YAML file from the filesystem and parses it as a configuration object.\n * By default, it will return an empty object if the file doesn't exist instead of throwing an error.\n * Supports both `.yaml` and `.yml` file extensions.\n *\n * @example\n * ```typescript\n * // Load from YAML file, ignore if missing\n * const source1 = new AppConfigSourceYaml('./config.yaml');\n *\n * // Load from YAML file, throw error if missing\n * const source2 = new AppConfigSourceYaml('./config.yaml', {\n * ignoreMissingFile: false\n * });\n *\n * // Load with custom encoding\n * const source3 = new AppConfigSourceYaml('./config.yaml', {\n * encoding: 'utf16le'\n * });\n * ```\n */\nexport class AppConfigSourceYaml implements AppConfigSource {\n private readonly options: AppConfigSourceFileOptions;\n\n /**\n * Creates a new AppConfigSourceYaml instance.\n *\n * @param filePath - The path to the YAML file to load.\n * @param options - Optional configuration for the source behavior.\n */\n constructor(\n private readonly filePath: string,\n options?: AppConfigSourceFileOptions,\n ) {\n this.options = {\n ignoreMissingFile: true,\n encoding: 'utf8',\n ...(options ?? {}),\n };\n }\n\n /**\n * Loads configuration from the YAML file.\n *\n * If the file doesn't exist and `ignoreMissingFile` is `true`, returns an empty object.\n * Otherwise, reads and parses the YAML file.\n *\n * @returns A promise that resolves to the parsed YAML configuration object.\n * @throws {Error} If the file doesn't exist and `ignoreMissingFile` is `false`,\n * or if the file contains invalid YAML.\n */\n async load(): Promise<Record<string, unknown>> {\n if (!existsSync(this.filePath) && this.options.ignoreMissingFile) {\n return {};\n }\n\n const file = await readFile(this.filePath, {\n encoding: this.options.encoding,\n });\n return YAML.parse(file.toString());\n }\n}\n","/**\n * Attempts to parse a string as JSON, returning the original string if parsing fails.\n *\n * @param text - The text to parse.\n * @returns The parsed JSON value, or the original text if parsing fails.\n */\nexport function tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text, (_, value) => value);\n } catch {\n return text;\n }\n}\n","import { AppConfigProvider } from '../app.config.provider.js';\nimport { ObjectVisitorMeta } from '../object.visitor.js';\nimport { tryParseJson } from '../helpers.js';\n\n/**\n * Provider that resolves environment variable references in configuration values.\n *\n * This provider matches string values using a regex pattern and replaces them with\n * values from `process.env`. The default pattern matches `${env:KEY}` and extracts\n * the key part to look up in the environment.\n *\n * After replacement, the result is attempted to be parsed as JSON. If parsing succeeds,\n * the parsed value is used; otherwise, the string value is used.\n *\n * @example\n * ```typescript\n * // With default pattern /\\$\\{env:(.+)\\}/g\n * // Value: \"${env:DATABASE_URL}\"\n * // Looks up: process.env.DATABASE_URL\n *\n * // Custom pattern\n * const provider = new AppConfigProviderDotenv(/\\$\\{([^}]+)\\}/g);\n * // Value: \"${DATABASE_URL}\"\n * // Looks up: process.env.DATABASE_URL\n * ```\n */\nexport class AppConfigProviderDotenv implements AppConfigProvider {\n private readonly prefix: RegExp;\n\n /**\n * Creates a new AppConfigProviderDotenv instance.\n *\n * @param prefix - A regex pattern or string to match environment variable references.\n * If a string is provided, it will be converted to a RegExp. The regex must have\n * at least one capture group that extracts the environment variable key.\n * Defaults to `/\\$\\{env:(.+)\\}/g` which matches `${env:KEY}` patterns.\n *\n * @example\n * ```typescript\n * // Default pattern\n * const provider1 = new AppConfigProviderDotenv();\n *\n * // Custom regex pattern\n * const provider2 = new AppConfigProviderDotenv(/\\$\\{([^}]+)\\}/g);\n *\n * // String pattern (converted to RegExp)\n * const provider3 = new AppConfigProviderDotenv('env:');\n * ```\n */\n constructor(prefix: string | RegExp = /\\$\\{env:(.+)\\}/g) {\n this.prefix = typeof prefix === 'string' ? new RegExp(prefix) : prefix;\n }\n\n /**\n * Checks if this provider can parse the given value.\n *\n * @param value - The string value to check.\n * @returns `true` if the value matches the provider's regex pattern, `false` otherwise.\n */\n canParse(value: string): boolean {\n return this.prefix.test(value);\n }\n\n /**\n * Parses the value by replacing environment variable references with actual values.\n *\n * The method:\n * 1. Finds all matches of the regex pattern in the value\n * 2. Replaces each match with the corresponding value from `process.env`\n * 3. Attempts to parse the result as JSON\n * 4. Updates the configuration object with the final value\n *\n * @param value - The string value containing environment variable references.\n * @param meta - Metadata about the value's location in the configuration object.\n * @returns A promise that resolves when the transformation is complete.\n *\n * @example\n * ```typescript\n * // If process.env.DATABASE_URL = \"postgres://localhost/db\"\n * // Value: \"${env:DATABASE_URL}\"\n * // Result: \"postgres://localhost/db\"\n *\n * // If process.env.PORT = \"3000\"\n * // Value: \"${env:PORT}\"\n * // Result: 3000 (parsed as JSON number)\n * ```\n */\n async parse(value: string, meta: ObjectVisitorMeta): Promise<void> {\n const matches = value.matchAll(this.prefix);\n\n let result = value;\n for (const [found, key] of matches) {\n result = result.replaceAll(found, process.env[key!] ?? '');\n }\n\n if (meta.arrayIndex !== undefined && Array.isArray(meta.owner)) {\n meta.owner[meta.arrayIndex] = tryParseJson(result ?? '');\n } else {\n (meta.owner as Record<string, unknown>)[meta.propertyPath] = tryParseJson(result ?? '');\n }\n }\n}\n","import { Injectable } from 'injectkit';\nimport { AppConfigProvider } from '../app.config.provider.js';\nimport { ObjectVisitorMeta } from '../object.visitor.js';\nimport { SecretManagerServiceClient } from '@google-cloud/secret-manager';\nimport { tryParseJson } from '../helpers.js';\n\n/**\n * Provider that resolves Google Cloud Platform Secret Manager references in configuration values.\n *\n * This provider matches string values using a regex pattern and replaces them with\n * secrets fetched from GCP Secret Manager. The default pattern matches `${gcp:SECRET_NAME}`\n * and extracts the secret name to look up in Secret Manager.\n *\n * After retrieval, the secret value is attempted to be parsed as JSON. If parsing succeeds,\n * the parsed value is used; otherwise, the string value is used.\n *\n * @remarks\n * This provider requires valid GCP credentials to be configured. It uses the\n * `@google-cloud/secret-manager` package and will use Application Default Credentials (ADC).\n *\n * @example\n * ```typescript\n * // With default pattern /\\$\\{gcp:(.+)\\}/g\n * // Value: \"${gcp:DATABASE_PASSWORD}\"\n * // Fetches: projects/{projectId}/secrets/DATABASE_PASSWORD/versions/latest\n *\n * const config = await new AppConfigBuilder()\n * .addSource(new AppConfigSourceJson('./config.json'))\n * .addProvider(new AppConfigProviderGcpSecrets('my-gcp-project'))\n * .build();\n * ```\n */\n@Injectable()\nexport class AppConfigProviderGcpSecrets implements AppConfigProvider {\n private readonly secretmanagerClient = new SecretManagerServiceClient();\n private readonly prefix: RegExp;\n\n /**\n * Creates a new AppConfigProviderGcpSecrets instance.\n *\n * @param projectId - The GCP project ID where secrets are stored.\n * @param prefix - A regex pattern or string to match secret references.\n * If a string is provided, it will be converted to a RegExp. The regex must have\n * at least one capture group that extracts the secret name.\n * Defaults to `/\\$\\{gcp:(.+)\\}/g` which matches `${gcp:SECRET_NAME}` patterns.\n *\n * @example\n * ```typescript\n * // Default pattern\n * const provider1 = new AppConfigProviderGcpSecrets('my-project');\n *\n * // Custom regex pattern\n * const provider2 = new AppConfigProviderGcpSecrets('my-project', /\\$\\{secret:([^}]+)\\}/g);\n * ```\n */\n constructor(\n private readonly projectId: string,\n prefix: string | RegExp = /\\$\\{gcp:(.+)\\}/g,\n ) {\n this.prefix = typeof prefix === 'string' ? new RegExp(prefix) : prefix;\n }\n\n /**\n * Checks if this provider can parse the given value.\n *\n * @param value - The string value to check.\n * @returns `true` if the value matches the provider's regex pattern, `false` otherwise.\n */\n canParse(value: string): boolean {\n return this.prefix.test(value);\n }\n\n /**\n * Fetches a secret from GCP Secret Manager.\n *\n * @param secretId - The name of the secret to fetch.\n * @returns A promise that resolves to the secret value, or an empty string if the secret\n * couldn't be fetched.\n * @internal\n */\n private async getSecret(secretId: string): Promise<string> {\n try {\n const [secret] = await this.secretmanagerClient.accessSecretVersion({\n name: `projects/${this.projectId}/secrets/${secretId}/versions/latest`,\n });\n return secret.payload?.data?.toString() ?? '';\n } catch (error) {\n console.error(error);\n return '';\n }\n }\n\n /**\n * Parses the value by replacing GCP secret references with actual secret values.\n *\n * The method:\n * 1. Finds all matches of the regex pattern in the value\n * 2. Fetches each secret from GCP Secret Manager in parallel\n * 3. Attempts to parse each result as JSON\n * 4. Updates the configuration object with the final value\n *\n * @param value - The string value containing GCP secret references.\n * @param meta - Metadata about the value's location in the configuration object.\n * @returns A promise that resolves when all secrets have been fetched and the\n * transformation is complete.\n *\n * @example\n * ```typescript\n * // If GCP secret \"API_KEY\" contains \"sk-abc123\"\n * // Value: \"${gcp:API_KEY}\"\n * // Result: \"sk-abc123\"\n *\n * // If GCP secret \"CONFIG\" contains '{\"retries\": 3}'\n * // Value: \"${gcp:CONFIG}\"\n * // Result: { retries: 3 } (parsed as JSON object)\n * ```\n */\n async parse(value: string, meta: ObjectVisitorMeta): Promise<void> {\n const tasks: Promise<void>[] = [];\n const matches = value.matchAll(this.prefix);\n\n for (const [, key] of matches) {\n const task = this.getSecret(key!).then(value => {\n if (meta.arrayIndex !== undefined && Array.isArray(meta.owner)) {\n meta.owner[meta.arrayIndex] = tryParseJson(value);\n } else {\n (meta.owner as Record<string, unknown>)[meta.propertyPath] = tryParseJson(value);\n }\n });\n tasks.push(task);\n }\n\n await Promise.all(tasks);\n }\n}\n"],"mappings":"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","names":["AppConfig","config","get","key","getString","String","getNumber","Number","getBoolean","Boolean","getObject","deepmerge","objectVisitor","obj","callback","visit","path","owner","propertyPath","arrayIndex","Array","isArray","forEach","item","index","entries","Object","entry","length","propertyType","AppConfigBuilder","sources","providers","addSource","source","push","addProvider","provider","build","sourceTasks","Promise","all","map","x","load","mergedConfig","deepmerge","tasks","parse","value","meta","find","canParse","objectVisitor","AppConfig","dotenv","AppConfigSourceDotenv","filePath","load","result","dotenv","config","path","quiet","error","Promise","resolve","parsed","existsSync","readFile","AppConfigSourceJson","options","filePath","ignoreMissingFile","encoding","load","existsSync","file","readFile","JSON","parse","toString","existsSync","readFile","YAML","AppConfigSourceYaml","options","filePath","ignoreMissingFile","encoding","load","existsSync","file","readFile","YAML","parse","toString","tryParseJson","text","JSON","parse","_","value","AppConfigProviderDotenv","prefix","RegExp","canParse","value","test","parse","meta","matches","matchAll","result","found","key","replaceAll","process","env","arrayIndex","undefined","Array","isArray","owner","tryParseJson","propertyPath","Injectable","SecretManagerServiceClient","AppConfigProviderGcpSecrets","secretmanagerClient","SecretManagerServiceClient","prefix","projectId","RegExp","canParse","value","test","getSecret","secretId","secret","accessSecretVersion","name","payload","data","toString","error","console","parse","meta","tasks","matches","matchAll","key","task","then","arrayIndex","undefined","Array","isArray","owner","tryParseJson","propertyPath","push","Promise","all"]}
|
|
1
|
+
{"version":3,"sources":["../src/app.config.ts","../src/app.config.builder.ts","../src/object.visitor.ts","../src/sources/app.config.source.dotenv.ts","../src/sources/app.config.source.json.ts","../src/sources/app.config.source.yaml.ts","../src/helpers.ts","../src/providers/app.config.provider.dotenv.ts","../src/providers/app.config.provider.gcp.secrets.ts"],"sourcesContent":["/**\n * Configuration container that provides type-safe access to configuration values.\n *\n * @template T - The type of the configuration object. Defaults to `Record<string, unknown>`.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({\n * database: { host: 'localhost', port: 5432 },\n * api: { timeout: 5000 }\n * });\n *\n * const host = config.get('database').host; // Type-safe access\n * ```\n */\nexport class AppConfig<T = Record<string, unknown>> {\n /**\n * Creates a new AppConfig instance with the provided configuration.\n *\n * @param config - The configuration object to wrap.\n */\n constructor(private readonly config: T) {}\n\n /**\n * Retrieves a configuration value by key.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value for the given key.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: 3000, host: 'localhost' });\n * const port = config.get('port'); // Returns 3000, typed as number\n * ```\n */\n get(key: keyof T): T[keyof T] {\n return this.config[key];\n }\n\n /**\n * Retrieves a configuration value cast to a specific type.\n *\n * Unlike `get()`, which returns `T[keyof T]`, this method lets you cast the\n * value to an arbitrary type `U`. Use this when the TypeScript type of the\n * stored value differs from what you need at the call site — for example,\n * when reading a nested object as a typed interface.\n *\n * @template U - The type to cast the value to.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value cast to `U`.\n *\n * @example\n * ```typescript\n * interface DbConfig { host: string; port: number }\n *\n * const config = new AppConfig({ database: { host: 'localhost', port: 5432 } });\n * const db = config.getAs<DbConfig>('database');\n * console.log(db.host); // 'localhost'\n * ```\n */\n getAs<U>(key: keyof T): U {\n return this.config[key] as U;\n }\n\n /**\n * Retrieves a configuration value as a string.\n *\n * The value is converted to a string using `String()`. This is useful when\n * you need to ensure a value is a string regardless of its original type.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a string.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: 3000, enabled: true });\n * const portStr = config.getString('port'); // Returns \"3000\"\n * const enabledStr = config.getString('enabled'); // Returns \"true\"\n * ```\n */\n getString(key: keyof T): string {\n return String(this.get(key));\n }\n\n /**\n * Retrieves a configuration value as a number.\n *\n * The value is converted to a number using `Number()`. This is useful when\n * you need to ensure a value is a number regardless of its original type.\n * Note: Invalid conversions will result in `NaN`.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a number.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ port: '3000', timeout: '5000' });\n * const port = config.getNumber('port'); // Returns 3000\n * const timeout = config.getNumber('timeout'); // Returns 5000\n * ```\n */\n getNumber(key: keyof T): number {\n return Number(this.config[key]);\n }\n\n /**\n * Retrieves a configuration value as a boolean.\n *\n * The value is converted to a boolean using `Boolean()`. This is useful when\n * you need to ensure a value is a boolean regardless of its original type.\n * Note: Only falsy values (false, 0, '', null, undefined, NaN) become false.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value converted to a boolean.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({ enabled: 'true', debug: 1 });\n * const enabled = config.getBoolean('enabled'); // Returns true\n * const debug = config.getBoolean('debug'); // Returns true\n * ```\n */\n getBoolean(key: keyof T): boolean {\n return Boolean(this.config[key]);\n }\n\n /**\n * Retrieves a configuration value as an object.\n *\n * The value is cast to an object type. This is useful when you know a value\n * is an object and want to access it with object methods.\n *\n * @template K - The key type, must be a key of T.\n * @param key - The configuration key to retrieve.\n * @returns The configuration value cast as an object.\n *\n * @example\n * ```typescript\n * const config = new AppConfig({\n * database: { host: 'localhost', port: 5432 }\n * });\n * const db = config.getObject('database'); // Returns { host: 'localhost', port: 5432 }\n * ```\n */\n getObject(key: keyof T): object {\n return this.config[key] as object;\n }\n}\n","import { deepmerge } from 'deepmerge-ts';\nimport { AppConfig } from './app.config.js';\nimport { AppConfigProvider } from './app.config.provider.js';\nimport { AppConfigSource } from './app.config.source.js';\nimport { objectVisitor, ObjectVisitorMeta } from './object.visitor.js';\n\n/**\n * Builder for constructing AppConfig instances from multiple sources with value transformation.\n *\n * The builder allows you to:\n * - Load configuration from multiple sources (files, environment variables, etc.)\n * - Merge configurations with later sources overriding earlier ones\n * - Transform string values using providers (e.g., resolving environment variable references)\n *\n * @example\n * ```typescript\n * const config = await new AppConfigBuilder()\n * .addSource(new AppConfigSourceJson('./config.json'))\n * .addSource(new AppConfigSourceDotenv())\n * .addProvider(new AppConfigProviderDotenv())\n * .build();\n * ```\n */\nexport class AppConfigBuilder {\n private readonly sources: AppConfigSource[] = [];\n private readonly providers: AppConfigProvider[] = [];\n\n /**\n * Adds a configuration source to the builder.\n *\n * Sources are loaded in the order they are added, and later sources override earlier ones\n * when merging configurations.\n *\n * @param source - The configuration source to add.\n * @returns The builder instance for method chaining.\n *\n * @example\n * ```typescript\n * builder\n * .addSource(new AppConfigSourceJson('./default.json'))\n * .addSource(new AppConfigSourceJson('./local.json'));\n * ```\n */\n addSource(source: AppConfigSource) {\n this.sources.push(source);\n return this;\n }\n\n /**\n * Adds a provider to transform string values during configuration building.\n *\n * Providers are applied to all string values found in the merged configuration.\n * The first provider that can parse a value will be used to transform it.\n *\n * @param provider - The provider to add.\n * @returns The builder instance for method chaining.\n *\n * @example\n * ```typescript\n * builder.addProvider(new AppConfigProviderDotenv());\n * ```\n */\n addProvider(provider: AppConfigProvider) {\n this.providers.push(provider);\n return this;\n }\n\n /**\n * Builds the AppConfig instance by loading all sources, merging them, and applying providers.\n *\n * The build process:\n * 1. Loads all sources in parallel\n * 2. Merges configurations (later sources override earlier ones)\n * 3. Traverses the merged configuration and applies providers to string values\n * 4. Returns the final AppConfig instance\n *\n * @template T - The type of the configuration object. Defaults to `Record<string, unknown>`.\n * @returns A promise that resolves to the built AppConfig instance.\n *\n * @example\n * ```typescript\n * const config = await builder.build<MyConfigType>();\n * const value = config.get('someKey');\n * ```\n */\n async build<T = Record<string, unknown>>(): Promise<AppConfig<T>> {\n const sourceTasks = await Promise.all(this.sources.map(x => x.load()));\n const mergedConfig = deepmerge(...sourceTasks) as T;\n\n const tasks: Promise<void>[] = [];\n const parse = (value: unknown, meta: ObjectVisitorMeta) => {\n if (typeof value === 'string') {\n const provider = this.providers.find(x => x.canParse(value));\n if (provider) {\n tasks.push(provider.parse(value, meta));\n }\n }\n };\n\n objectVisitor(mergedConfig, parse);\n await Promise.all(tasks);\n\n return new AppConfig<T>(mergedConfig);\n }\n}\n","/**\n * Metadata about a value's location within an object structure.\n */\nexport type ObjectVisitorMeta = {\n /** The full path to the value (e.g., \"database.host\" or \"items[0]\"). */\n path: string;\n /** The object that owns this property. */\n owner: object;\n /** The property name or array index path (e.g., \"host\" or \"items[0]\"). */\n propertyPath: string;\n /** The type of the property value. */\n propertyType: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';\n /** The array index if the value is in an array, undefined otherwise. */\n arrayIndex?: number;\n};\n\n/**\n * Callback function invoked for each primitive value found during object traversal.\n *\n * @param value - The primitive value found.\n * @param meta - Metadata about the value's location in the object structure.\n */\nexport type ObjectVisitorCallback = (value: unknown, meta: ObjectVisitorMeta) => void;\n\n/**\n * Traverses an object structure and invokes a callback for each primitive value found.\n *\n * The visitor recursively traverses objects and arrays, calling the callback for each\n * primitive value (string, number, boolean, bigint) encountered. It skips functions,\n * symbols, null, and undefined values.\n *\n * @param obj - The object to traverse. Can be any value.\n * @param callback - The callback function to invoke for each primitive value.\n *\n * @example\n * ```typescript\n * const config = {\n * database: { host: 'localhost', port: 5432 },\n * items: ['a', 'b', 'c']\n * };\n *\n * objectVisitor(config, (value, meta) => {\n * console.log(`${meta.path} = ${value}`);\n * });\n * // Output:\n * // database.host = localhost\n * // database.port = 5432\n * // items[0] = a\n * // items[1] = b\n * // items[2] = c\n * ```\n */\nexport const objectVisitor = (obj: unknown, callback: ObjectVisitorCallback): void => {\n const visit = (\n obj: unknown,\n callback: ObjectVisitorCallback,\n path: string = '',\n owner: object = {},\n propertyPath: string = '',\n arrayIndex?: number,\n ): void => {\n if (!obj) {\n return;\n }\n\n switch (typeof obj) {\n case 'object':\n if (Array.isArray(obj)) {\n obj.forEach((item, index) => {\n visit(item, callback, path + `[${index}]`, obj, propertyPath + `[${index}]`, index);\n });\n } else {\n const entries = Object.entries(obj);\n for (const entry of entries) {\n visit(entry[1], callback, path + (path.length > 0 ? '.' : '') + entry[0], obj, entry[0]);\n }\n }\n break;\n case 'function':\n case 'symbol':\n case 'undefined':\n break;\n default:\n callback(obj, {\n owner,\n propertyPath,\n path,\n propertyType: typeof obj,\n arrayIndex,\n });\n break;\n }\n };\n\n visit(obj, callback);\n};\n","import { AppConfigSource } from '../app.config.source.js';\nimport dotenv from 'dotenv';\n\n/**\n * Configuration source that loads environment variables from a `.env` file.\n *\n * This source uses the `dotenv` package to load environment variables from a `.env` file.\n * If no file path is provided, it will look for a `.env` file in the current working directory.\n * All values are strings as provided by the environment file.\n *\n * @example\n * ```typescript\n * // Load from default .env file\n * const source1 = new AppConfigSourceDotenv();\n * const config1 = await source1.load();\n *\n * // Load from custom path\n * const source2 = new AppConfigSourceDotenv('./config/.env.local');\n * const config2 = await source2.load();\n * ```\n */\nexport class AppConfigSourceDotenv implements AppConfigSource {\n /**\n * Creates a new AppConfigSourceDotenv instance.\n *\n * @param filePath - Optional path to the `.env` file. If not provided, `dotenv` will\n * look for a `.env` file in the current working directory.\n */\n constructor(private readonly filePath?: string) {}\n\n /**\n * Loads environment variables from the `.env` file.\n *\n * Uses `dotenv.config()` to parse the file and load variables into the returned object.\n * If the file doesn't exist or there's an error, an error will be thrown.\n *\n * @returns A promise that resolves to an object containing the parsed environment variables.\n * @throws {Error} If there's an error reading or parsing the `.env` file.\n */\n async load(): Promise<Record<string, unknown>> {\n const result = dotenv.config({ path: this.filePath, quiet: true });\n if (result.error) {\n throw result.error;\n }\n return Promise.resolve(result.parsed ?? {});\n }\n}\n","import { existsSync } from 'node:fs';\nimport { AppConfigSource } from '../app.config.source.js';\nimport { readFile } from 'node:fs/promises';\nimport { AppConfigSourceFileOptions } from '../app.config.source.options.js';\n\n/**\n * Configuration source that loads configuration from a JSON file.\n *\n * This source reads a JSON file from the filesystem and parses it as a configuration object.\n * By default, it will return an empty object if the file doesn't exist instead of throwing an error.\n *\n * @example\n * ```typescript\n * // Load from JSON file, ignore if missing\n * const source1 = new AppConfigSourceJson('./config.json');\n *\n * // Load from JSON file, throw error if missing\n * const source2 = new AppConfigSourceJson('./config.json', {\n * ignoreMissingFile: false\n * });\n *\n * // Load with custom encoding\n * const source3 = new AppConfigSourceJson('./config.json', {\n * encoding: 'utf16le'\n * });\n * ```\n */\nexport class AppConfigSourceJson implements AppConfigSource {\n private readonly options: AppConfigSourceFileOptions;\n\n /**\n * Creates a new AppConfigSourceJson instance.\n *\n * @param filePath - The path to the JSON file to load.\n * @param options - Optional configuration for the source behavior.\n */\n constructor(\n private readonly filePath: string,\n options?: AppConfigSourceFileOptions,\n ) {\n this.options = {\n ignoreMissingFile: true,\n encoding: 'utf8',\n ...(options ?? {}),\n };\n }\n\n /**\n * Loads configuration from the JSON file.\n *\n * If the file doesn't exist and `ignoreMissingFile` is `true`, returns an empty object.\n * Otherwise, reads and parses the JSON file.\n *\n * @returns A promise that resolves to the parsed JSON configuration object.\n * @throws {Error} If the file doesn't exist and `ignoreMissingFile` is `false`,\n * or if the file contains invalid JSON.\n */\n async load(): Promise<Record<string, unknown>> {\n if (!existsSync(this.filePath) && this.options.ignoreMissingFile) {\n return {};\n }\n\n const file = await readFile(this.filePath, {\n encoding: this.options.encoding,\n });\n return JSON.parse(file.toString());\n }\n}\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport YAML from 'yaml';\nimport { AppConfigSource } from '../app.config.source.js';\nimport { AppConfigSourceFileOptions } from '../app.config.source.options.js';\n\n/**\n * Configuration source that loads configuration from a YAML file.\n *\n * This source reads a YAML file from the filesystem and parses it as a configuration object.\n * By default, it will return an empty object if the file doesn't exist instead of throwing an error.\n * Supports both `.yaml` and `.yml` file extensions.\n *\n * @example\n * ```typescript\n * // Load from YAML file, ignore if missing\n * const source1 = new AppConfigSourceYaml('./config.yaml');\n *\n * // Load from YAML file, throw error if missing\n * const source2 = new AppConfigSourceYaml('./config.yaml', {\n * ignoreMissingFile: false\n * });\n *\n * // Load with custom encoding\n * const source3 = new AppConfigSourceYaml('./config.yaml', {\n * encoding: 'utf16le'\n * });\n * ```\n */\nexport class AppConfigSourceYaml implements AppConfigSource {\n private readonly options: AppConfigSourceFileOptions;\n\n /**\n * Creates a new AppConfigSourceYaml instance.\n *\n * @param filePath - The path to the YAML file to load.\n * @param options - Optional configuration for the source behavior.\n */\n constructor(\n private readonly filePath: string,\n options?: AppConfigSourceFileOptions,\n ) {\n this.options = {\n ignoreMissingFile: true,\n encoding: 'utf8',\n ...(options ?? {}),\n };\n }\n\n /**\n * Loads configuration from the YAML file.\n *\n * If the file doesn't exist and `ignoreMissingFile` is `true`, returns an empty object.\n * Otherwise, reads and parses the YAML file.\n *\n * @returns A promise that resolves to the parsed YAML configuration object.\n * @throws {Error} If the file doesn't exist and `ignoreMissingFile` is `false`,\n * or if the file contains invalid YAML.\n */\n async load(): Promise<Record<string, unknown>> {\n if (!existsSync(this.filePath) && this.options.ignoreMissingFile) {\n return {};\n }\n\n const file = await readFile(this.filePath, {\n encoding: this.options.encoding,\n });\n return YAML.parse(file.toString());\n }\n}\n","/**\n * Attempts to parse a string as JSON, returning the original string if parsing fails.\n *\n * @param text - The text to parse.\n * @returns The parsed JSON value, or the original text if parsing fails.\n */\nexport function tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text, (_, value) => value);\n } catch {\n return text;\n }\n}\n","import { AppConfigProvider } from '../app.config.provider.js';\nimport { ObjectVisitorMeta } from '../object.visitor.js';\nimport { tryParseJson } from '../helpers.js';\n\n/**\n * Provider that resolves environment variable references in configuration values.\n *\n * This provider matches string values using a regex pattern and replaces them with\n * values from `process.env`. The default pattern matches `${env:KEY}` and extracts\n * the key part to look up in the environment.\n *\n * After replacement, the result is attempted to be parsed as JSON. If parsing succeeds,\n * the parsed value is used; otherwise, the string value is used.\n *\n * @example\n * ```typescript\n * // With default pattern /\\$\\{env:(.+)\\}/g\n * // Value: \"${env:DATABASE_URL}\"\n * // Looks up: process.env.DATABASE_URL\n *\n * // Custom pattern\n * const provider = new AppConfigProviderDotenv(/\\$\\{([^}]+)\\}/g);\n * // Value: \"${DATABASE_URL}\"\n * // Looks up: process.env.DATABASE_URL\n * ```\n */\nexport class AppConfigProviderDotenv implements AppConfigProvider {\n private readonly prefix: RegExp;\n\n /**\n * Creates a new AppConfigProviderDotenv instance.\n *\n * @param prefix - A regex pattern or string to match environment variable references.\n * If a string is provided, it will be converted to a RegExp. The regex must have\n * at least one capture group that extracts the environment variable key.\n * Defaults to `/\\$\\{env:(.+)\\}/g` which matches `${env:KEY}` patterns.\n *\n * @example\n * ```typescript\n * // Default pattern\n * const provider1 = new AppConfigProviderDotenv();\n *\n * // Custom regex pattern\n * const provider2 = new AppConfigProviderDotenv(/\\$\\{([^}]+)\\}/g);\n *\n * // String pattern (converted to RegExp)\n * const provider3 = new AppConfigProviderDotenv('env:');\n * ```\n */\n constructor(prefix: string | RegExp = /\\$\\{env:(.+)\\}/g) {\n this.prefix = typeof prefix === 'string' ? new RegExp(prefix) : prefix;\n }\n\n /**\n * Checks if this provider can parse the given value.\n *\n * @param value - The string value to check.\n * @returns `true` if the value matches the provider's regex pattern, `false` otherwise.\n */\n canParse(value: string): boolean {\n this.prefix.lastIndex = 0;\n return this.prefix.test(value);\n }\n\n /**\n * Parses the value by replacing environment variable references with actual values.\n *\n * The method:\n * 1. Finds all matches of the regex pattern in the value\n * 2. Replaces each match with the corresponding value from `process.env`\n * 3. Attempts to parse the result as JSON\n * 4. Updates the configuration object with the final value\n *\n * @param value - The string value containing environment variable references.\n * @param meta - Metadata about the value's location in the configuration object.\n * @returns A promise that resolves when the transformation is complete.\n *\n * @example\n * ```typescript\n * // If process.env.DATABASE_URL = \"postgres://localhost/db\"\n * // Value: \"${env:DATABASE_URL}\"\n * // Result: \"postgres://localhost/db\"\n *\n * // If process.env.PORT = \"3000\"\n * // Value: \"${env:PORT}\"\n * // Result: 3000 (parsed as JSON number)\n * ```\n */\n async parse(value: string, meta: ObjectVisitorMeta): Promise<void> {\n const matches = value.matchAll(this.prefix);\n\n let result = value;\n for (const [found, key] of matches) {\n result = result.replaceAll(found, process.env[key!] ?? '');\n }\n\n if (meta.arrayIndex !== undefined && Array.isArray(meta.owner)) {\n meta.owner[meta.arrayIndex] = tryParseJson(result ?? '');\n } else {\n (meta.owner as Record<string, unknown>)[meta.propertyPath] = tryParseJson(result ?? '');\n }\n }\n}\n","import { Injectable } from 'injectkit';\nimport { AppConfigProvider } from '../app.config.provider.js';\nimport { ObjectVisitorMeta } from '../object.visitor.js';\nimport { SecretManagerServiceClient } from '@google-cloud/secret-manager';\nimport { tryParseJson } from '../helpers.js';\n\n/**\n * Provider that resolves Google Cloud Platform Secret Manager references in configuration values.\n *\n * This provider matches string values using a regex pattern and replaces them with\n * secrets fetched from GCP Secret Manager. The default pattern matches `${gcp:SECRET_NAME}`\n * and extracts the secret name to look up in Secret Manager.\n *\n * After retrieval, the secret value is attempted to be parsed as JSON. If parsing succeeds,\n * the parsed value is used; otherwise, the string value is used.\n *\n * @remarks\n * This provider requires valid GCP credentials to be configured. It uses the\n * `@google-cloud/secret-manager` package and will use Application Default Credentials (ADC).\n *\n * @example\n * ```typescript\n * // With default pattern /\\$\\{gcp:(.+)\\}/g\n * // Value: \"${gcp:DATABASE_PASSWORD}\"\n * // Fetches: projects/{projectId}/secrets/DATABASE_PASSWORD/versions/latest\n *\n * const config = await new AppConfigBuilder()\n * .addSource(new AppConfigSourceJson('./config.json'))\n * .addProvider(new AppConfigProviderGcpSecrets('my-gcp-project'))\n * .build();\n * ```\n */\n@Injectable()\nexport class AppConfigProviderGcpSecrets implements AppConfigProvider {\n private readonly secretmanagerClient = new SecretManagerServiceClient();\n private readonly prefix: RegExp;\n\n /**\n * Creates a new AppConfigProviderGcpSecrets instance.\n *\n * @param projectId - The GCP project ID where secrets are stored.\n * @param prefix - A regex pattern or string to match secret references.\n * If a string is provided, it will be converted to a RegExp. The regex must have\n * at least one capture group that extracts the secret name.\n * Defaults to `/\\$\\{gcp:(.+)\\}/g` which matches `${gcp:SECRET_NAME}` patterns.\n *\n * @example\n * ```typescript\n * // Default pattern\n * const provider1 = new AppConfigProviderGcpSecrets('my-project');\n *\n * // Custom regex pattern\n * const provider2 = new AppConfigProviderGcpSecrets('my-project', /\\$\\{secret:([^}]+)\\}/g);\n * ```\n */\n constructor(\n private readonly projectId: string,\n prefix: string | RegExp = /\\$\\{gcp:(.+)\\}/g,\n ) {\n this.prefix = typeof prefix === 'string' ? new RegExp(prefix) : prefix;\n }\n\n /**\n * Checks if this provider can parse the given value.\n *\n * @param value - The string value to check.\n * @returns `true` if the value matches the provider's regex pattern, `false` otherwise.\n */\n canParse(value: string): boolean {\n return this.prefix.test(value);\n }\n\n /**\n * Fetches a secret from GCP Secret Manager.\n *\n * @param secretId - The name of the secret to fetch.\n * @returns A promise that resolves to the secret value, or an empty string if the secret\n * couldn't be fetched.\n * @internal\n */\n private async getSecret(secretId: string): Promise<string> {\n try {\n const [secret] = await this.secretmanagerClient.accessSecretVersion({\n name: `projects/${this.projectId}/secrets/${secretId}/versions/latest`,\n });\n return secret.payload?.data?.toString() ?? '';\n } catch (error) {\n console.error(error);\n return '';\n }\n }\n\n /**\n * Parses the value by replacing GCP secret references with actual secret values.\n *\n * The method:\n * 1. Finds all matches of the regex pattern in the value\n * 2. Fetches each secret from GCP Secret Manager in parallel\n * 3. Attempts to parse each result as JSON\n * 4. Updates the configuration object with the final value\n *\n * @param value - The string value containing GCP secret references.\n * @param meta - Metadata about the value's location in the configuration object.\n * @returns A promise that resolves when all secrets have been fetched and the\n * transformation is complete.\n *\n * @example\n * ```typescript\n * // If GCP secret \"API_KEY\" contains \"sk-abc123\"\n * // Value: \"${gcp:API_KEY}\"\n * // Result: \"sk-abc123\"\n *\n * // If GCP secret \"CONFIG\" contains '{\"retries\": 3}'\n * // Value: \"${gcp:CONFIG}\"\n * // Result: { retries: 3 } (parsed as JSON object)\n * ```\n */\n async parse(value: string, meta: ObjectVisitorMeta): Promise<void> {\n const tasks: Promise<void>[] = [];\n const matches = value.matchAll(this.prefix);\n\n for (const [, key] of matches) {\n const task = this.getSecret(key!).then(value => {\n if (meta.arrayIndex !== undefined && Array.isArray(meta.owner)) {\n meta.owner[meta.arrayIndex] = tryParseJson(value);\n } else {\n (meta.owner as Record<string, unknown>)[meta.propertyPath] = tryParseJson(value);\n }\n });\n tasks.push(task);\n }\n\n await Promise.all(tasks);\n }\n}\n"],"mappings":"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","names":["AppConfig","config","get","key","getAs","getString","String","getNumber","Number","getBoolean","Boolean","getObject","deepmerge","objectVisitor","obj","callback","visit","path","owner","propertyPath","arrayIndex","Array","isArray","forEach","item","index","entries","Object","entry","length","propertyType","AppConfigBuilder","sources","providers","addSource","source","push","addProvider","provider","build","sourceTasks","Promise","all","map","x","load","mergedConfig","deepmerge","tasks","parse","value","meta","find","canParse","objectVisitor","AppConfig","dotenv","AppConfigSourceDotenv","filePath","load","result","dotenv","config","path","quiet","error","Promise","resolve","parsed","existsSync","readFile","AppConfigSourceJson","options","filePath","ignoreMissingFile","encoding","load","existsSync","file","readFile","JSON","parse","toString","existsSync","readFile","YAML","AppConfigSourceYaml","options","filePath","ignoreMissingFile","encoding","load","existsSync","file","readFile","YAML","parse","toString","tryParseJson","text","JSON","parse","_","value","AppConfigProviderDotenv","prefix","RegExp","canParse","value","lastIndex","test","parse","meta","matches","matchAll","result","found","key","replaceAll","process","env","arrayIndex","undefined","Array","isArray","owner","tryParseJson","propertyPath","Injectable","SecretManagerServiceClient","AppConfigProviderGcpSecrets","secretmanagerClient","SecretManagerServiceClient","prefix","projectId","RegExp","canParse","value","test","getSecret","secretId","secret","accessSecretVersion","name","payload","data","toString","error","console","parse","meta","tasks","matches","matchAll","key","task","then","arrayIndex","undefined","Array","isArray","owner","tryParseJson","propertyPath","push","Promise","all"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.config.provider.dotenv.d.ts","sourceRoot":"","sources":["../../src/providers/app.config.provider.dotenv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;;;;;;;;;;;;;;;OAmBG;gBACS,MAAM,GAAE,MAAM,GAAG,MAA0B;IAIvD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"app.config.provider.dotenv.d.ts","sourceRoot":"","sources":["../../src/providers/app.config.provider.dotenv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;;;;;;;;;;;;;;;OAmBG;gBACS,MAAM,GAAE,MAAM,GAAG,MAA0B;IAIvD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAKhC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CAcnE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedsoftware/appconfig",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A flexible, type-safe configuration management library with support for multiple sources and value transformation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marooned Software",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"deepmerge-ts": "^7.1.5",
|
|
34
|
-
"dotenv": "^17.
|
|
35
|
-
"injectkit": "^1.
|
|
34
|
+
"dotenv": "^17.3.1",
|
|
35
|
+
"injectkit": "^1.1.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@google-cloud/secret-manager": "^6.1.1",
|
|
39
39
|
"yaml": "^2.8.2",
|
|
40
|
-
"@repo/config-
|
|
41
|
-
"@repo/config-
|
|
40
|
+
"@repo/config-typescript": "0.0.0",
|
|
41
|
+
"@repo/config-eslint": "0.1.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@google-cloud/secret-manager": "^6.1.1",
|