@kibibit/configit 1.0.0-beta.26 → 1.0.0-beta.27
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 +419 -0
- package/lib/scripts/test-vault-comprehensive.d.ts +2 -0
- package/lib/scripts/test-vault-comprehensive.d.ts.map +1 -0
- package/lib/scripts/test-vault-comprehensive.js +422 -0
- package/lib/scripts/test-vault-comprehensive.js.map +1 -0
- package/lib/scripts/test-vault-dynamic.d.ts +2 -0
- package/lib/scripts/test-vault-dynamic.d.ts.map +1 -0
- package/lib/scripts/test-vault-dynamic.js +193 -0
- package/lib/scripts/test-vault-dynamic.js.map +1 -0
- package/lib/scripts/test-vault-gcp-ttl.d.ts +3 -0
- package/lib/scripts/test-vault-gcp-ttl.d.ts.map +1 -0
- package/lib/scripts/test-vault-gcp-ttl.js +218 -0
- package/lib/scripts/test-vault-gcp-ttl.js.map +1 -0
- package/lib/scripts/test-vault.d.ts +2 -0
- package/lib/scripts/test-vault.d.ts.map +1 -0
- package/lib/scripts/test-vault.js +167 -0
- package/lib/scripts/test-vault.js.map +1 -0
- package/lib/src/config.errors.d.ts.map +1 -0
- package/lib/src/config.errors.js.map +1 -0
- package/lib/src/config.model.d.ts.map +1 -0
- package/lib/src/config.model.js.map +1 -0
- package/lib/{config.service.d.ts → src/config.service.d.ts} +10 -1
- package/lib/src/config.service.d.ts.map +1 -0
- package/lib/{config.service.js → src/config.service.js} +75 -9
- package/lib/src/config.service.js.map +1 -0
- package/lib/src/environment.service.d.ts.map +1 -0
- package/lib/src/environment.service.js.map +1 -0
- package/lib/{index.d.ts → src/index.d.ts} +1 -0
- package/lib/src/index.d.ts.map +1 -0
- package/lib/{index.js → src/index.js} +1 -0
- package/lib/src/index.js.map +1 -0
- package/lib/src/json-schema.validator.d.ts.map +1 -0
- package/lib/src/json-schema.validator.js.map +1 -0
- package/lib/src/vault/__tests__/vault-integration.test.d.ts +2 -0
- package/lib/src/vault/__tests__/vault-integration.test.d.ts.map +1 -0
- package/lib/src/vault/__tests__/vault-integration.test.js +190 -0
- package/lib/src/vault/__tests__/vault-integration.test.js.map +1 -0
- package/lib/src/vault/decorators.d.ts +17 -0
- package/lib/src/vault/decorators.d.ts.map +1 -0
- package/lib/src/vault/decorators.js +149 -0
- package/lib/src/vault/decorators.js.map +1 -0
- package/lib/src/vault/index.d.ts +7 -0
- package/lib/src/vault/index.d.ts.map +1 -0
- package/lib/src/vault/index.js +42 -0
- package/lib/src/vault/index.js.map +1 -0
- package/lib/src/vault/secret-refresh-manager.d.ts +23 -0
- package/lib/src/vault/secret-refresh-manager.d.ts.map +1 -0
- package/lib/src/vault/secret-refresh-manager.js +149 -0
- package/lib/src/vault/secret-refresh-manager.js.map +1 -0
- package/lib/src/vault/types.d.ts +149 -0
- package/lib/src/vault/types.d.ts.map +1 -0
- package/lib/src/vault/types.js +4 -0
- package/lib/src/vault/types.js.map +1 -0
- package/lib/src/vault/vault-cache.d.ts +20 -0
- package/lib/src/vault/vault-cache.d.ts.map +1 -0
- package/lib/src/vault/vault-cache.js +139 -0
- package/lib/src/vault/vault-cache.js.map +1 -0
- package/lib/src/vault/vault-integration.d.ts +27 -0
- package/lib/src/vault/vault-integration.d.ts.map +1 -0
- package/lib/src/vault/vault-integration.js +211 -0
- package/lib/src/vault/vault-integration.js.map +1 -0
- package/lib/src/vault/vault-provider.d.ts +37 -0
- package/lib/src/vault/vault-provider.d.ts.map +1 -0
- package/lib/src/vault/vault-provider.js +354 -0
- package/lib/src/vault/vault-provider.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -65
- package/src/config.service.ts +155 -10
- package/src/config.service.vault.spec.ts +859 -0
- package/src/index.ts +1 -0
- package/src/vault/__tests__/vault-integration.test.ts +226 -0
- package/src/vault/decorators.ts +228 -0
- package/src/vault/index.ts +31 -0
- package/src/vault/secret-refresh-manager.ts +241 -0
- package/src/vault/types.ts +487 -0
- package/src/vault/vault-cache.ts +240 -0
- package/src/vault/vault-integration.ts +332 -0
- package/src/vault/vault-provider.ts +576 -0
- package/lib/config.errors.d.ts.map +0 -1
- package/lib/config.errors.js.map +0 -1
- package/lib/config.model.d.ts.map +0 -1
- package/lib/config.model.js.map +0 -1
- package/lib/config.service.d.ts.map +0 -1
- package/lib/config.service.js.map +0 -1
- package/lib/environment.service.d.ts.map +0 -1
- package/lib/environment.service.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/json-schema.validator.d.ts.map +0 -1
- package/lib/json-schema.validator.js.map +0 -1
- /package/lib/{config.errors.d.ts → src/config.errors.d.ts} +0 -0
- /package/lib/{config.errors.js → src/config.errors.js} +0 -0
- /package/lib/{config.model.d.ts → src/config.model.d.ts} +0 -0
- /package/lib/{config.model.js → src/config.model.js} +0 -0
- /package/lib/{environment.service.d.ts → src/environment.service.d.ts} +0 -0
- /package/lib/{environment.service.js → src/environment.service.js} +0 -0
- /package/lib/{json-schema.validator.d.ts → src/json-schema.validator.d.ts} +0 -0
- /package/lib/{json-schema.validator.js → src/json-schema.validator.js} +0 -0
package/src/config.service.ts
CHANGED
|
@@ -22,6 +22,7 @@ import * as nconfJsoncFormat from '@kibibit/nconf-jsonc';
|
|
|
22
22
|
import { ConfigValidationError } from './config.errors';
|
|
23
23
|
import { BaseConfig } from './config.model';
|
|
24
24
|
import { getEnvironment, setEnvironment } from './environment.service';
|
|
25
|
+
import { IVaultConfigOptions, VaultHealth, VaultIntegration } from './vault';
|
|
25
26
|
|
|
26
27
|
type INconfKibibitFormats = IFormats & {
|
|
27
28
|
yaml: nconfYamlFormat;
|
|
@@ -45,6 +46,7 @@ export interface IConfigServiceOptions {
|
|
|
45
46
|
algorithm: string;
|
|
46
47
|
secret: string;
|
|
47
48
|
};
|
|
49
|
+
vault?: IVaultConfigOptions;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export enum EFileFormats {
|
|
@@ -76,13 +78,14 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
76
78
|
private fileExtension: EFileFormats;
|
|
77
79
|
readonly mode: string = getEnvironment();
|
|
78
80
|
readonly options: IConfigServiceOptions;
|
|
79
|
-
|
|
81
|
+
private _config?: T;
|
|
80
82
|
readonly genericClass?: TClass<T>;
|
|
81
83
|
readonly fileName?: string;
|
|
82
84
|
readonly configFileName: string = '';
|
|
83
85
|
readonly configFileFullPath?: string;
|
|
84
86
|
readonly configFileRoot?: string;
|
|
85
87
|
readonly appRoot: string;
|
|
88
|
+
private vaultIntegration?: VaultIntegration;
|
|
86
89
|
|
|
87
90
|
constructor(
|
|
88
91
|
givenClass: TClass<T>,
|
|
@@ -107,8 +110,8 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
107
110
|
this.appRoot = this.findRoot();
|
|
108
111
|
this.genericClass = givenClass;
|
|
109
112
|
this.fileExtension = this.options.fileFormat || EFileFormats.json;
|
|
110
|
-
this.
|
|
111
|
-
this.configFileName = this.
|
|
113
|
+
this._config = this.createConfigInstance(this.genericClass, {}) as T;
|
|
114
|
+
this.configFileName = this._config.getFileName(this.fileExtension);
|
|
112
115
|
this.configFileRoot = this.findConfigRoot();
|
|
113
116
|
this.configFileFullPath = join(
|
|
114
117
|
this.configFileRoot,
|
|
@@ -117,12 +120,25 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
117
120
|
|
|
118
121
|
this.initializeNconf();
|
|
119
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Two-Phase Initialization Pattern:
|
|
125
|
+
*
|
|
126
|
+
* Phase 1 (Constructor - Synchronous):
|
|
127
|
+
* - Create VaultIntegration instance if configured (not initialized yet)
|
|
128
|
+
* - This allows Vault to be optional and maintains backward compatibility
|
|
129
|
+
* - Actual initialization happens in initializeVault() (Phase 2)
|
|
130
|
+
*/
|
|
131
|
+
if (this.options.vault) {
|
|
132
|
+
this.vaultIntegration = new VaultIntegration(this.options.vault);
|
|
133
|
+
// Note: Not initialized yet - will be done in initializeVault()
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
const config = passedConfig || nconf.get();
|
|
121
137
|
|
|
122
138
|
const pathDoesNotExist = pathExistsSync(this.configFileFullPath) === false;
|
|
123
139
|
if (pathDoesNotExist && (config.saveToFile || config.init)) {
|
|
124
140
|
console.log(cyan('Initializing Configuration File'));
|
|
125
|
-
this.
|
|
141
|
+
this._config = this.createConfigInstance(this.genericClass, {}) as T;
|
|
126
142
|
this.writeConfigToFile();
|
|
127
143
|
this.writeSchema();
|
|
128
144
|
console.log(cyan('EXITING'));
|
|
@@ -142,7 +158,7 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
142
158
|
}
|
|
143
159
|
if (!envConfig) { return; }
|
|
144
160
|
envConfig.NODE_ENV = this.mode;
|
|
145
|
-
this.
|
|
161
|
+
this._config = this.createConfigInstance(this.genericClass, envConfig as T) as T;
|
|
146
162
|
|
|
147
163
|
if (config.saveToFile || config.init) {
|
|
148
164
|
if (config.convert) {
|
|
@@ -159,9 +175,138 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
159
175
|
configService = this;
|
|
160
176
|
}
|
|
161
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Getter for config property with Vault initialization guard
|
|
180
|
+
*
|
|
181
|
+
* Phase 3 (Runtime Access - Synchronous):
|
|
182
|
+
* - Returns config synchronously (Vault secrets already loaded via nconf.overrides())
|
|
183
|
+
* - Warns if Vault is configured but not initialized
|
|
184
|
+
*/
|
|
185
|
+
get config(): T {
|
|
186
|
+
// Guard: Warn if Vault is configured but not initialized
|
|
187
|
+
if (this.options.vault && this.vaultIntegration && !this.vaultIntegration.isInitialized()) {
|
|
188
|
+
console.warn(
|
|
189
|
+
'Warning: Vault is configured but not initialized. ' +
|
|
190
|
+
'Call await configService.initializeVault() before accessing config. ' +
|
|
191
|
+
'Config may not include Vault secrets.'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!this._config) {
|
|
196
|
+
throw new Error('ConfigService config not initialized');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return this._config;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Phase 2: Vault Initialization (Async)
|
|
204
|
+
*
|
|
205
|
+
* Initializes Vault connection, authenticates, and loads secrets.
|
|
206
|
+
* Secrets are injected into nconf.overrides() (highest priority).
|
|
207
|
+
* Config is re-created with Vault secrets included.
|
|
208
|
+
*
|
|
209
|
+
* Error Handling:
|
|
210
|
+
* - If fallback.required === false: Logs warning and continues without Vault secrets
|
|
211
|
+
* - If fallback.required === true (default): Throws error and fails fast
|
|
212
|
+
*
|
|
213
|
+
* @throws {Error} If Vault initialization fails and fallback.required !== false
|
|
214
|
+
*/
|
|
215
|
+
async initializeVault(): Promise<void> {
|
|
216
|
+
if (!this.options.vault) {
|
|
217
|
+
return; // Vault not configured - no-op
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!this.vaultIntegration) {
|
|
221
|
+
// Should not happen if constructor ran correctly
|
|
222
|
+
throw new Error('VaultIntegration not created. Check constructor.');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!this.genericClass) {
|
|
226
|
+
throw new Error('ConfigService not properly initialized');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Initialize Vault connection and authenticate
|
|
231
|
+
await this.vaultIntegration.initialize();
|
|
232
|
+
|
|
233
|
+
// Load secrets for this config class
|
|
234
|
+
// This will call VaultCache.set() which injects into nconf.overrides()
|
|
235
|
+
await this.vaultIntegration.loadSecrets(this.genericClass as unknown as new () => T);
|
|
236
|
+
|
|
237
|
+
// Now that Vault secrets are in nconf.overrides(), re-validate config
|
|
238
|
+
const config = nconf.get(); // Now includes Vault secrets (highest priority)
|
|
239
|
+
const envConfig = this.validateInput(config);
|
|
240
|
+
|
|
241
|
+
if (envConfig) {
|
|
242
|
+
envConfig.NODE_ENV = this.mode;
|
|
243
|
+
// Update config instance with Vault secrets included
|
|
244
|
+
this._config = this.createConfigInstance(
|
|
245
|
+
this.genericClass,
|
|
246
|
+
envConfig as T
|
|
247
|
+
) as T;
|
|
248
|
+
}
|
|
249
|
+
} catch (error: any) {
|
|
250
|
+
// Handle initialization failure based on fallback config
|
|
251
|
+
const fallback = this.options.vault.fallback;
|
|
252
|
+
|
|
253
|
+
if (fallback?.required !== false) {
|
|
254
|
+
// Required - rethrow error
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Vault initialization failed: ${ error?.message || 'Unknown error' }. ` +
|
|
257
|
+
`Vault is required for this configuration.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Optional - log warning and continue with existing config
|
|
262
|
+
console.warn(
|
|
263
|
+
`Vault initialization failed: ${ error?.message || 'Unknown error' }. ` +
|
|
264
|
+
`Continuing without Vault secrets.`
|
|
265
|
+
);
|
|
266
|
+
// Config already created without Vault secrets - that's fine
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get Vault health status
|
|
272
|
+
*/
|
|
273
|
+
getVaultHealth(): VaultHealth | null {
|
|
274
|
+
if (!this.vaultIntegration) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return this.vaultIntegration.getHealth();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Invalidate Vault cache for a specific path
|
|
282
|
+
*/
|
|
283
|
+
invalidateVaultCache(vaultPath: string): void {
|
|
284
|
+
if (this.vaultIntegration) {
|
|
285
|
+
this.vaultIntegration.invalidateCache(vaultPath);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Invalidate Vault cache for a property
|
|
291
|
+
*/
|
|
292
|
+
invalidateVaultProperty(propertyName: string): void {
|
|
293
|
+
if (this.vaultIntegration) {
|
|
294
|
+
this.vaultIntegration.invalidateProperty(propertyName);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Shutdown Vault integration gracefully
|
|
300
|
+
*/
|
|
301
|
+
shutdownVault(): void {
|
|
302
|
+
if (this.vaultIntegration) {
|
|
303
|
+
this.vaultIntegration.shutdown();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
162
307
|
toPlainObject() {
|
|
163
308
|
// hope this works now!
|
|
164
|
-
return classToPlain(new this.genericClass(this.
|
|
309
|
+
return classToPlain(new this.genericClass(this._config));
|
|
165
310
|
}
|
|
166
311
|
|
|
167
312
|
writeConfigToFile(
|
|
@@ -309,13 +454,13 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
309
454
|
sharedConfigsSchemas.push(sharedConfigSchema);
|
|
310
455
|
}
|
|
311
456
|
|
|
312
|
-
const schema = this.
|
|
457
|
+
const schema = this._config.toJsonSchema();
|
|
313
458
|
const schemaFullPath = join(
|
|
314
459
|
this.configFileRoot,
|
|
315
460
|
'/',
|
|
316
461
|
this.options.schemaFolderName,
|
|
317
462
|
'/',
|
|
318
|
-
this.
|
|
463
|
+
this._config.getSchemaFileName()
|
|
319
464
|
);
|
|
320
465
|
|
|
321
466
|
let sharedConfigsProperties = {};
|
|
@@ -367,7 +512,7 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
367
512
|
}
|
|
368
513
|
|
|
369
514
|
private writeSharedConfigToFile(configClass: TClass<BaseConfig>) {
|
|
370
|
-
const config = this.createConfigInstance(configClass, this.
|
|
515
|
+
const config = this.createConfigInstance(configClass, this._config);
|
|
371
516
|
const plainConfig = classToPlain(config);
|
|
372
517
|
const relativePathToSchema = relative(
|
|
373
518
|
this.configFileRoot,
|
|
@@ -460,7 +605,7 @@ export class ConfigService<T extends BaseConfig> {
|
|
|
460
605
|
|
|
461
606
|
if (validationErrors.length > 0) {
|
|
462
607
|
const validationError = new ConfigValidationError(validationErrors);
|
|
463
|
-
const errorMessageTitle = `${ startCase(this.
|
|
608
|
+
const errorMessageTitle = `${ startCase(this._config.name) } Configuration Errors`;
|
|
464
609
|
const titleBar = this.generateTerminalTitleBar(errorMessageTitle);
|
|
465
610
|
console.error(titleBar, validationError.message);
|
|
466
611
|
|