@kibibit/configit 1.0.0-beta.25 → 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.
Files changed (98) hide show
  1. package/README.md +419 -0
  2. package/lib/scripts/test-vault-comprehensive.d.ts +2 -0
  3. package/lib/scripts/test-vault-comprehensive.d.ts.map +1 -0
  4. package/lib/scripts/test-vault-comprehensive.js +422 -0
  5. package/lib/scripts/test-vault-comprehensive.js.map +1 -0
  6. package/lib/scripts/test-vault-dynamic.d.ts +2 -0
  7. package/lib/scripts/test-vault-dynamic.d.ts.map +1 -0
  8. package/lib/scripts/test-vault-dynamic.js +193 -0
  9. package/lib/scripts/test-vault-dynamic.js.map +1 -0
  10. package/lib/scripts/test-vault-gcp-ttl.d.ts +3 -0
  11. package/lib/scripts/test-vault-gcp-ttl.d.ts.map +1 -0
  12. package/lib/scripts/test-vault-gcp-ttl.js +218 -0
  13. package/lib/scripts/test-vault-gcp-ttl.js.map +1 -0
  14. package/lib/scripts/test-vault.d.ts +2 -0
  15. package/lib/scripts/test-vault.d.ts.map +1 -0
  16. package/lib/scripts/test-vault.js +167 -0
  17. package/lib/scripts/test-vault.js.map +1 -0
  18. package/lib/src/config.errors.d.ts.map +1 -0
  19. package/lib/src/config.errors.js.map +1 -0
  20. package/lib/src/config.model.d.ts.map +1 -0
  21. package/lib/src/config.model.js.map +1 -0
  22. package/lib/{config.service.d.ts → src/config.service.d.ts} +10 -1
  23. package/lib/src/config.service.d.ts.map +1 -0
  24. package/lib/{config.service.js → src/config.service.js} +75 -9
  25. package/lib/src/config.service.js.map +1 -0
  26. package/lib/src/environment.service.d.ts.map +1 -0
  27. package/lib/src/environment.service.js.map +1 -0
  28. package/lib/{index.d.ts → src/index.d.ts} +1 -0
  29. package/lib/src/index.d.ts.map +1 -0
  30. package/lib/{index.js → src/index.js} +1 -0
  31. package/lib/src/index.js.map +1 -0
  32. package/lib/src/json-schema.validator.d.ts.map +1 -0
  33. package/lib/src/json-schema.validator.js.map +1 -0
  34. package/lib/src/vault/__tests__/vault-integration.test.d.ts +2 -0
  35. package/lib/src/vault/__tests__/vault-integration.test.d.ts.map +1 -0
  36. package/lib/src/vault/__tests__/vault-integration.test.js +190 -0
  37. package/lib/src/vault/__tests__/vault-integration.test.js.map +1 -0
  38. package/lib/src/vault/decorators.d.ts +17 -0
  39. package/lib/src/vault/decorators.d.ts.map +1 -0
  40. package/lib/src/vault/decorators.js +149 -0
  41. package/lib/src/vault/decorators.js.map +1 -0
  42. package/lib/src/vault/index.d.ts +7 -0
  43. package/lib/src/vault/index.d.ts.map +1 -0
  44. package/lib/src/vault/index.js +42 -0
  45. package/lib/src/vault/index.js.map +1 -0
  46. package/lib/src/vault/secret-refresh-manager.d.ts +23 -0
  47. package/lib/src/vault/secret-refresh-manager.d.ts.map +1 -0
  48. package/lib/src/vault/secret-refresh-manager.js +149 -0
  49. package/lib/src/vault/secret-refresh-manager.js.map +1 -0
  50. package/lib/src/vault/types.d.ts +149 -0
  51. package/lib/src/vault/types.d.ts.map +1 -0
  52. package/lib/src/vault/types.js +4 -0
  53. package/lib/src/vault/types.js.map +1 -0
  54. package/lib/src/vault/vault-cache.d.ts +20 -0
  55. package/lib/src/vault/vault-cache.d.ts.map +1 -0
  56. package/lib/src/vault/vault-cache.js +139 -0
  57. package/lib/src/vault/vault-cache.js.map +1 -0
  58. package/lib/src/vault/vault-integration.d.ts +27 -0
  59. package/lib/src/vault/vault-integration.d.ts.map +1 -0
  60. package/lib/src/vault/vault-integration.js +211 -0
  61. package/lib/src/vault/vault-integration.js.map +1 -0
  62. package/lib/src/vault/vault-provider.d.ts +37 -0
  63. package/lib/src/vault/vault-provider.d.ts.map +1 -0
  64. package/lib/src/vault/vault-provider.js +354 -0
  65. package/lib/src/vault/vault-provider.js.map +1 -0
  66. package/lib/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +14 -74
  68. package/src/config.service.ts +155 -10
  69. package/src/config.service.vault.spec.ts +859 -0
  70. package/src/index.ts +1 -0
  71. package/src/vault/__tests__/vault-integration.test.ts +226 -0
  72. package/src/vault/decorators.ts +228 -0
  73. package/src/vault/index.ts +31 -0
  74. package/src/vault/secret-refresh-manager.ts +241 -0
  75. package/src/vault/types.ts +487 -0
  76. package/src/vault/vault-cache.ts +240 -0
  77. package/src/vault/vault-integration.ts +332 -0
  78. package/src/vault/vault-provider.ts +576 -0
  79. package/lib/config.errors.d.ts.map +0 -1
  80. package/lib/config.errors.js.map +0 -1
  81. package/lib/config.model.d.ts.map +0 -1
  82. package/lib/config.model.js.map +0 -1
  83. package/lib/config.service.d.ts.map +0 -1
  84. package/lib/config.service.js.map +0 -1
  85. package/lib/environment.service.d.ts.map +0 -1
  86. package/lib/environment.service.js.map +0 -1
  87. package/lib/index.d.ts.map +0 -1
  88. package/lib/index.js.map +0 -1
  89. package/lib/json-schema.validator.d.ts.map +0 -1
  90. package/lib/json-schema.validator.js.map +0 -1
  91. /package/lib/{config.errors.d.ts → src/config.errors.d.ts} +0 -0
  92. /package/lib/{config.errors.js → src/config.errors.js} +0 -0
  93. /package/lib/{config.model.d.ts → src/config.model.d.ts} +0 -0
  94. /package/lib/{config.model.js → src/config.model.js} +0 -0
  95. /package/lib/{environment.service.d.ts → src/environment.service.d.ts} +0 -0
  96. /package/lib/{environment.service.js → src/environment.service.js} +0 -0
  97. /package/lib/{json-schema.validator.d.ts → src/json-schema.validator.d.ts} +0 -0
  98. /package/lib/{json-schema.validator.js → src/json-schema.validator.js} +0 -0
@@ -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
- readonly config?: T;
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.config = this.createConfigInstance(this.genericClass, {}) as T;
111
- this.configFileName = this.config.getFileName(this.fileExtension);
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.config = this.createConfigInstance(this.genericClass, {}) as T;
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.config = this.createConfigInstance(this.genericClass, envConfig as T) as T;
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.config));
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.config.toJsonSchema();
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.config.getSchemaFileName()
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.config);
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.config.name) } Configuration Errors`;
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