@incanta/config 2.1.7 → 2.1.8

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.
Files changed (2) hide show
  1. package/README.md +421 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,421 @@
1
+ # @incanta/config
2
+
3
+ A hierarchical, folder-based configuration library for Node.js with environment inheritance, variable cross-referencing, kebab-to-camelCase conversion, secrets provider integration, and support for YAML, JSON, JSONC, and JSON5 file formats.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @incanta/config
9
+ # or
10
+ yarn add @incanta/config
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import config from "@incanta/config";
17
+
18
+ const port = config.get<number>("server.port");
19
+ const debug = config.get<boolean>("environment.development");
20
+ ```
21
+
22
+ By default, `@incanta/config` looks for a `config` directory in the current working directory and loads the `default` environment. You can override this with the `NODE_CONFIG_DIR` and `NODE_CONFIG_ENV` environment variables, or by creating a new `Config` instance directly.
23
+
24
+ ## Core Concepts
25
+
26
+ ### Directory-Based Configuration
27
+
28
+ Configuration values are defined by the folder structure, file names, and object keys within those files. Each subfolder and each file (minus its extension) adds a segment to the config key path.
29
+
30
+ Given this structure:
31
+
32
+ ```
33
+ config/
34
+ default/
35
+ _index.yaml
36
+ server.yaml
37
+ persistence/
38
+ database.yaml
39
+ redis.yaml
40
+ ```
41
+
42
+ - `_index.yaml` contents are scoped to the parent folder (no path segment added)
43
+ - `server.yaml` values are accessed under `server.*`
44
+ - `persistence/database.yaml` values are accessed under `persistence.database.*`
45
+ - `persistence/redis.yaml` values are accessed under `persistence.redis.*`
46
+
47
+ For example, if `server.yaml` contains:
48
+
49
+ ```yaml
50
+ port: 3000
51
+ host: "localhost"
52
+ ```
53
+
54
+ You access these values with:
55
+
56
+ ```typescript
57
+ config.get<number>("server.port"); // 3000
58
+ config.get<string>("server.host"); // "localhost"
59
+ ```
60
+
61
+ ### The `_index.yaml` / `index.yaml` File
62
+
63
+ Index files are special — they do **not** add a path segment. Their contents are scoped to the parent directory. For example, `config/default/_index.yaml`:
64
+
65
+ ```yaml
66
+ environment:
67
+ development: true
68
+ ```
69
+
70
+ This is accessed as `environment.development`, not `_index.environment.development`. The underscore prefix is optional but recommended to keep the file at the top of file explorers.
71
+
72
+ ### Supported File Formats
73
+
74
+ - **YAML** (`.yaml`, `.yml`)
75
+ - **JSON** (`.json`)
76
+ - **JSONC** (`.jsonc`) — JSON with comments
77
+ - **JSON5** (`.json5`)
78
+
79
+ You can mix file formats within the same config directory.
80
+
81
+ ## Environments
82
+
83
+ Environments let you override default values for different deployment targets (e.g. `staging`, `production`). Only values that differ from the default need to be specified in the environment folder.
84
+
85
+ ```
86
+ config/
87
+ default/ # base values (always loaded first)
88
+ server.yaml
89
+ staging/ # overrides for staging
90
+ server.yaml
91
+ production/ # overrides for production
92
+ server.yaml
93
+ ```
94
+
95
+ Set the environment via:
96
+
97
+ - **Environment variable**: `NODE_CONFIG_ENV=production`
98
+ - **Constructor option**: `new Config({ configEnv: "production" })`
99
+ - **`config.init()` call**: `config.init({ configEnv: "production" })`
100
+ - **Using [config-env](#environment-variable-injection-config-env)**: `config-env --env=production <command>`
101
+
102
+ When an environment is active, its values are deep merged on top of `default`. Arrays are **replaced** entirely (not merged).
103
+
104
+ ### Environment Inheritance
105
+
106
+ Environments inherit from `default` automatically. For more complex setups, you can inherit from additional environments using a `_config.json` file inside the environment folder:
107
+
108
+ ```
109
+ config/
110
+ default/
111
+ staging/
112
+ _config.json # { "parentNames": ["kubernetes"] }
113
+ kubernetes/
114
+ production/
115
+ _config.json # { "parentNames": ["staging"] }
116
+ ```
117
+
118
+ **`production/_config.json`:**
119
+ ```json
120
+ {
121
+ "parentNames": ["staging"]
122
+ }
123
+ ```
124
+
125
+ This creates an inheritance chain: `default` → `kubernetes` → `staging` → `production`. Values are loaded and merged in that order, with later environments taking precedence.
126
+
127
+ You can specify multiple parents for complex inheritance:
128
+
129
+ ```json
130
+ {
131
+ "parentNames": ["production", "cloud-aws"]
132
+ }
133
+ ```
134
+
135
+ Inheritance is resolved in order: `default` → `production` (and its parents) → `cloud-aws` → current environment.
136
+
137
+ ### Override File
138
+
139
+ An `override.json` file placed directly in the config directory root (not inside any environment folder) is loaded last and merges on top of everything:
140
+
141
+ ```
142
+ config/
143
+ default/
144
+ production/
145
+ override.json # applied last, on top of the active environment
146
+ ```
147
+
148
+ ## Variable Cross-Referencing
149
+
150
+ Config values can reference other config values using `${path.to.variable}` syntax:
151
+
152
+ ```yaml
153
+ # docker.yaml
154
+ image-prefix: "myregistry/myapp"
155
+
156
+ # server.yaml
157
+ image: "${docker.imagePrefix}-server"
158
+ ```
159
+
160
+ `config.get<string>("server.image")` resolves to `"myregistry/myapp-server"`.
161
+
162
+ ### Relative References
163
+
164
+ You can use relative paths with a leading dot (`.`):
165
+
166
+ ```yaml
167
+ # realm/instances/default.yaml
168
+ key: "default"
169
+ namespace: "realm-${./key}" # resolves to "realm-default"
170
+ ```
171
+
172
+ Relative paths are resolved from the current object's location in the config tree.
173
+
174
+ ### Recursive Resolution
175
+
176
+ References are resolved recursively, so a reference can point to another value that itself contains a reference.
177
+
178
+ ## Kebab-Case to camelCase Conversion
179
+
180
+ By default, kebab-case keys are automatically converted to camelCase. Both forms can be used to access the value:
181
+
182
+ ```yaml
183
+ # server.yaml
184
+ max-connections: 100
185
+ debug-port: 9229
186
+ ```
187
+
188
+ ```typescript
189
+ config.get<number>("server.maxConnections"); // 100
190
+ config.get<number>("server.max-connections"); // 100
191
+ ```
192
+
193
+ ### Variable Casing Options
194
+
195
+ You can control this behavior with a `variableCasing` setting in `_config.json` (or as a key in any config object):
196
+
197
+ | Value | Behavior |
198
+ |-------|----------|
199
+ | `"camel"` | Only camelCase keys are available (default) |
200
+ | `"original"` | Only original keys are preserved |
201
+ | `"both"` | Both the original and camelCase keys are available |
202
+
203
+ ```json
204
+ {
205
+ "variableCasing": "original"
206
+ }
207
+ ```
208
+
209
+ ## Config Base (Template Objects)
210
+
211
+ You can define a base/template object that other sibling objects inherit from using `incantaConfigBase`:
212
+
213
+ ```yaml
214
+ # instances/_index.yaml
215
+ incantaConfigBase: "base"
216
+ ```
217
+
218
+ ```yaml
219
+ # instances/base.yaml
220
+ foo: "bar"
221
+ hello: "world"
222
+ num: 42
223
+ ```
224
+
225
+ ```yaml
226
+ # instances/custom.yaml
227
+ hello: "city"
228
+ ```
229
+
230
+ The `custom` object will inherit values from `base` and override only the specified keys:
231
+
232
+ ```typescript
233
+ config.get<string>("instances.custom.foo"); // "bar" (inherited)
234
+ config.get<string>("instances.custom.hello"); // "city" (overridden)
235
+ config.get<number>("instances.custom.num"); // 42 (inherited)
236
+ ```
237
+
238
+ ## Secrets
239
+
240
+ `@incanta/config` has built-in support for fetching secrets from external providers. Any config value prefixed with `secret|` is treated as a secret reference:
241
+
242
+ ```yaml
243
+ password: "secret|my-secret-key"
244
+ ```
245
+
246
+ ### Supported Providers
247
+
248
+ | Provider | Config Value |
249
+ |----------|-------------|
250
+ | HashiCorp Vault | `"vault"` |
251
+ | AWS Secrets Manager | `"aws-secrets-manager"` |
252
+ | Azure Key Vault | `"azure-key-vault"` |
253
+ | GCP Secret Manager | `"gcp-secret-manager"` |
254
+ | Local (file-based) | `"local"` |
255
+ | None (disabled) | `"none"` |
256
+
257
+ Configure the provider in your config files:
258
+
259
+ ```yaml
260
+ # secrets.yaml
261
+ provider: "none"
262
+ cache-duration-seconds: 300
263
+ ```
264
+
265
+ ### Fetching Secrets
266
+
267
+ Use `getWithSecrets()` to resolve secret references:
268
+
269
+ ```typescript
270
+ const dbConfig = await config.getWithSecrets<IDatabaseConfig>(
271
+ "persistence.database"
272
+ );
273
+ ```
274
+
275
+ This recursively walks the returned object and replaces any `"secret|..."` strings with values fetched from the configured secrets provider. Results are cached according to `secrets.cache-duration-seconds`.
276
+
277
+ ## Environment Variable Injection (`config-env`)
278
+
279
+ The package includes a `config-env` CLI tool that injects config values as environment variables into a child process. This tool will also inject the `NODE_CONFIG_ENV` variable to the child process.
280
+
281
+ Define an `environment.yaml` (or `environment.json`, etc.) file in the config directory root mapping environment variable names to config paths:
282
+
283
+ ```yaml
284
+ # environment.yaml
285
+ DATABASE_HOST: "persistence.database.host"
286
+ DATABASE_PORT: "persistence.database.port"
287
+ ```
288
+
289
+ ### CLI Usage
290
+
291
+ ```bash
292
+ npx config-env <command>
293
+
294
+ # With a specific config environment:
295
+ npx config-env --env=production <command>
296
+ npx config-env -e=production <command>
297
+ ```
298
+
299
+ ### Programmatic Usage
300
+
301
+ ```typescript
302
+ import config from "@incanta/config";
303
+
304
+ const envVars = config.getConfiguredEnv();
305
+ // { DATABASE_HOST: "localhost", DATABASE_PORT: "5432" }
306
+ ```
307
+
308
+ ## `config-settings.json`
309
+
310
+ Place a `config-settings.json` file in the working directory root to customize the config system's behavior:
311
+
312
+ ```jsonc
313
+ {
314
+ "defaults": {
315
+ "dir": "config/node", // default config directory (default: "config")
316
+ "env": "my-default-env" // default environment name (default: "default")
317
+ },
318
+ "extraDirs": [
319
+ "/path/to/additional/config" // extra directories to search for environments
320
+ ]
321
+ }
322
+ ```
323
+
324
+ ### Extra Directories
325
+
326
+ `extraDirs` allows environment folders to live outside the main config directory. This is useful when you want to keep project-specific configuration separate from shared configuration:
327
+
328
+ ```jsonc
329
+ {
330
+ "extraDirs": [
331
+ "C:\\path\\to\\MyProject\\Config\\Backend"
332
+ ]
333
+ }
334
+ ```
335
+
336
+ When resolving an environment name, the library first checks the main config directory, then checks each extra directory in order.
337
+
338
+ ## API Reference
339
+
340
+ ### Default Export (Singleton)
341
+
342
+ ```typescript
343
+ import config from "@incanta/config";
344
+ ```
345
+
346
+ Returns a singleton `Config` instance, initialized with defaults on first access.
347
+
348
+ ### `Config` Class
349
+
350
+ ```typescript
351
+ import Config from "@incanta/config/config";
352
+ ```
353
+
354
+ #### Constructor
355
+
356
+ ```typescript
357
+ const config = new Config(options?: IConfigOptions);
358
+ ```
359
+
360
+ | Option | Type | Description |
361
+ |--------|------|-------------|
362
+ | `configDir` | `string` | Path to the config directory |
363
+ | `configEnv` | `string` | Environment name to load |
364
+ | `cwd` | `string` | Working directory for resolving relative paths |
365
+
366
+ #### Methods
367
+
368
+ | Method | Returns | Description |
369
+ |--------|---------|-------------|
370
+ | `init(options?)` | `void` | Re-initialize with new options |
371
+ | `get<T>(key)` | `T` | Get a value by dot-separated path; throws if not found |
372
+ | `tryGet<T>(key)` | `T \| null` | Get a value, returning `null` if the key doesn't exist |
373
+ | `getWithSecrets<T>(key)` | `Promise<T>` | Get a value and resolve any `secret\|...` references |
374
+ | `processSecrets<T>(value)` | `Promise<T>` | Recursively resolve secrets in an arbitrary value |
375
+ | `getConfiguredEnv()` | `object` | Get env vars mapped from the `environment.*` config file |
376
+ | `getJson()` | `object` | Get the entire resolved config as a plain object |
377
+ | `set<T>(key, value)` | `void` | Set a runtime config value |
378
+ | `dir()` | `string` | Get the active config directory path |
379
+ | `env()` | `string` | Get the active environment name |
380
+ | `cwd()` | `string` | Get the working directory |
381
+
382
+ ### Environment Variables
383
+
384
+ | Variable | Description |
385
+ |----------|-------------|
386
+ | `NODE_CONFIG_DIR` | Override the config directory path |
387
+ | `NODE_CONFIG_ENV` | Override the config environment name |
388
+ | `NODE_CONFIG_SKIP_ENV_WARNING` | Set to `"true"` to suppress missing environment warnings |
389
+
390
+ ## Example Project Structure
391
+
392
+ A typical project using `@incanta/config`:
393
+
394
+ ```
395
+ my-project/
396
+ config-settings.json # optional: customize defaults and extra dirs
397
+ config/
398
+ environment.yaml # env var mappings for config-env CLI
399
+ override.json # optional: final overrides
400
+ default/ # base configuration
401
+ _index.yaml
402
+ server.yaml
403
+ persistence/
404
+ database.yaml
405
+ redis.yaml
406
+ staging/ # staging overrides
407
+ _config.json # { "parentNames": ["kubernetes"] }
408
+ _index.yaml
409
+ kubernetes/ # kubernetes-specific values
410
+ server.yaml
411
+ production/ # production overrides
412
+ _config.json # { "parentNames": ["staging"] }
413
+ persistence/
414
+ database.yaml
415
+ src/
416
+ index.ts
417
+ ```
418
+
419
+ ## License
420
+
421
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incanta/config",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "main": "lib/index.js",
5
5
  "exports": {
6
6
  ".": "./lib/index.js",
@@ -19,7 +19,7 @@
19
19
  "audit": "yarn-audit-fix"
20
20
  },
21
21
  "dependencies": {
22
- "@aws-sdk/client-secrets-manager": "^3.839.0",
22
+ "@aws-sdk/client-secrets-manager": "^3.985.0",
23
23
  "@azure/identity": "^4.10.1",
24
24
  "@azure/keyvault-secrets": "^4.10.0",
25
25
  "@google-cloud/secret-manager": "^6.1.1",