@@ -71,6 +71,7 @@ const CONFIG_INFO_KEY = {
7171 [ CONFIG_KEY . TYPE ] : true ,
7272 [ CONFIG_KEY . ACTIVE ] : true ,
7373 [ CONFIG_KEY . SOURCE ] : true ,
74+ [ CONFIG_KEY . SOURCE_FILEPATH ] : true ,
7475 [ CONFIG_KEY . APP_URL ] : true ,
7576 [ CONFIG_KEY . APP_URL_ACTIVE ] : true ,
7677 [ CONFIG_KEY . VALIDATIONS ] : true ,
@@ -237,6 +238,9 @@ class FeatureToggles {
237238 for ( const [ featureKey , value ] of entries ) {
238239 if ( this . __config [ featureKey ] ) {
239240 switch ( mergeConflictBehavior ) {
241+ case CONFIG_MERGE_CONFLICT . OVERRIDE : {
242+ break ;
243+ }
240244 case CONFIG_MERGE_CONFLICT . PRESERVE : {
241245 continue ;
242246 }
@@ -274,26 +278,51 @@ class FeatureToggles {
274278 ...( sourceFilepath && { sourceFilepath } ) ,
275279 } ,
276280 } ,
277- "feature configuration is not an object"
281+ "configuration is not an object"
278282 ) ;
279283 }
280284
281285 const { type, active, appUrl, fallbackValue, validations } = value ;
282286
283- this . __featureKeys . push ( featureKey ) ;
287+ if ( [ undefined , null ] . includes ( fallbackValue ) ) {
288+ throw new VError (
289+ {
290+ name : VERROR_CLUSTER_NAME ,
291+ info : {
292+ featureKey,
293+ source,
294+ ...( sourceFilepath && { sourceFilepath } ) ,
295+ } ,
296+ } ,
297+ "configuration has no or invalid fallback value"
298+ ) ;
299+ }
300+
301+ if ( ! FEATURE_VALID_TYPES . includes ( type ) ) {
302+ throw new VError (
303+ {
304+ name : VERROR_CLUSTER_NAME ,
305+ info : {
306+ featureKey,
307+ source,
308+ ...( sourceFilepath && { sourceFilepath } ) ,
309+ } ,
310+ } ,
311+ "configuration has no or invalid type"
312+ ) ;
313+ }
314+
284315 this . __fallbackValues [ featureKey ] = fallbackValue ;
285316 this . __config [ featureKey ] = { } ;
286317
318+ this . __config [ featureKey ] [ CONFIG_KEY . TYPE ] = type ;
319+
287320 this . __config [ featureKey ] [ CONFIG_KEY . SOURCE ] = source ;
288321
289322 if ( sourceFilepath ) {
290323 this . __config [ featureKey ] [ CONFIG_KEY . SOURCE_FILEPATH ] = sourceFilepath ;
291324 }
292325
293- if ( type ) {
294- this . __config [ featureKey ] [ CONFIG_KEY . TYPE ] = type ;
295- }
296-
297326 if ( active === false ) {
298327 this . __config [ featureKey ] [ CONFIG_KEY . ACTIVE ] = false ;
299328 }
@@ -308,7 +337,6 @@ class FeatureToggles {
308337
309338 if ( validations ) {
310339 this . __config [ featureKey ] [ CONFIG_KEY . VALIDATIONS ] = validations ;
311- this . _processValidations ( featureKey , validations , sourceFilepath ) ;
312340 }
313341 }
314342
@@ -318,25 +346,35 @@ class FeatureToggles {
318346 /**
319347 * Populate this.__config.
320348 */
321- _processConfig ( { configRuntime, configFromFilesEntries, configAuto } = { } ) {
322- const configRuntimeCount = this . _processConfigSource (
323- CONFIG_SOURCE . RUNTIME ,
324- CONFIG_MERGE_CONFLICT . THROW ,
325- configRuntime
326- ) ;
349+ _processConfig ( { configAuto, configFromFilesEntries, configRuntime } = { } ) {
350+ const configAutoCount = this . _processConfigSource ( CONFIG_SOURCE . AUTO , CONFIG_MERGE_CONFLICT . OVERRIDE , configAuto ) ;
327351 const configFromFileCount = configFromFilesEntries . reduce (
328352 ( count , [ configFilepath , configFromFile ] ) =>
329353 count +
330- this . _processConfigSource ( CONFIG_SOURCE . FILE , CONFIG_MERGE_CONFLICT . THROW , configFromFile , configFilepath ) ,
354+ this . _processConfigSource ( CONFIG_SOURCE . FILE , CONFIG_MERGE_CONFLICT . OVERRIDE , configFromFile , configFilepath ) ,
331355 0
332356 ) ;
333- const configAutoCount = this . _processConfigSource ( CONFIG_SOURCE . AUTO , CONFIG_MERGE_CONFLICT . PRESERVE , configAuto ) ;
357+ const configRuntimeCount = this . _processConfigSource (
358+ CONFIG_SOURCE . RUNTIME ,
359+ CONFIG_MERGE_CONFLICT . OVERRIDE ,
360+ configRuntime
361+ ) ;
362+
363+ // NOTE: this post-processing is easier to do after the configuration is merged
364+ this . __featureKeys = Object . keys ( this . __fallbackValues ) ;
365+ for ( const featureKey of this . __featureKeys ) {
366+ const validations = this . __config [ featureKey ] [ CONFIG_KEY . VALIDATIONS ] ;
367+ if ( validations ) {
368+ const sourceFilepath = this . __config [ featureKey ] [ CONFIG_KEY . SOURCE_FILEPATH ] ;
369+ this . _processValidations ( featureKey , validations , sourceFilepath ) ;
370+ }
371+ }
334372
335373 this . __isConfigProcessed = true ;
336374 return {
375+ [ CONFIG_SOURCE . AUTO ] : configAutoCount ,
337376 [ CONFIG_SOURCE . RUNTIME ] : configRuntimeCount ,
338377 [ CONFIG_SOURCE . FILE ] : configFromFileCount ,
339- [ CONFIG_SOURCE . AUTO ] : configAutoCount ,
340378 } ;
341379 }
342380
@@ -804,10 +842,10 @@ class FeatureToggles {
804842 * @param {InitializeOptions } [options]
805843 */
806844 async _initializeFeatures ( {
807- config : configRuntime ,
845+ configAuto ,
808846 configFile : configFilepath ,
809847 configFiles : configFilepaths ,
810- configAuto ,
848+ config : configRuntime ,
811849 customRedisCredentials,
812850 customRedisClientOptions,
813851 } = { } ) {
@@ -841,9 +879,9 @@ class FeatureToggles {
841879 let toggleCounts ;
842880 try {
843881 toggleCounts = this . _processConfig ( {
844- configRuntime,
845- configFromFilesEntries,
846882 configAuto,
883+ configFromFilesEntries,
884+ configRuntime,
847885 } ) ;
848886 } catch ( err ) {
849887 throw new VError (
@@ -924,17 +962,17 @@ class FeatureToggles {
924962 }
925963
926964 const totalCount =
927- toggleCounts [ CONFIG_SOURCE . RUNTIME ] + toggleCounts [ CONFIG_SOURCE . FILE ] + toggleCounts [ CONFIG_SOURCE . AUTO ] ;
965+ toggleCounts [ CONFIG_SOURCE . AUTO ] + toggleCounts [ CONFIG_SOURCE . FILE ] + toggleCounts [ CONFIG_SOURCE . RUNTIME ] ;
928966 logger . info (
929967 [
930968 "finished initialization" ,
931969 ...( this . __uniqueName ? [ `of "${ this . __uniqueName } "` ] : [ ] ) ,
932970 util . format (
933- "with %i feature toggles (%i runtime , %i file, %i auto )" ,
971+ "with %i feature toggles (%i auto , %i file, %i runtime )" ,
934972 totalCount ,
935- toggleCounts [ CONFIG_SOURCE . RUNTIME ] ,
973+ toggleCounts [ CONFIG_SOURCE . AUTO ] ,
936974 toggleCounts [ CONFIG_SOURCE . FILE ] ,
937- toggleCounts [ CONFIG_SOURCE . AUTO ]
975+ toggleCounts [ CONFIG_SOURCE . RUNTIME ]
938976 ) ,
939977 `using ${ redisIntegrationMode } ` ,
940978 ] . join ( " " )
@@ -964,12 +1002,17 @@ class FeatureToggles {
9641002 * @param {InitializeOptions } [options]
9651003 */
9661004 async initializeFeatures ( options ) {
967- if ( ! this . __initializePromise ) {
968- this . __initializePromise = this . _initializeFeatures ( options ) ;
1005+ if ( this . __initializePromise ) {
1006+ throw new VError ( { name : VERROR_CLUSTER_NAME } , "already initialized" ) ;
9691007 }
1008+ this . __initializePromise = this . _initializeFeatures ( options ) ;
9701009 return await this . __initializePromise ;
9711010 }
9721011
1012+ get canInitialize ( ) {
1013+ return ! this . __initializePromise ;
1014+ }
1015+
9731016 // ========================================
9741017 // END OF INITIALIZE SECTION
9751018 // ========================================
0 commit comments