@@ -76,6 +76,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
76
76
#featureFlagRefreshTimer: RefreshTimer ;
77
77
78
78
// selectors
79
+ #keyValueSelectors: PagedSettingSelector [ ] = [ ] ;
79
80
#featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
80
81
81
82
constructor (
@@ -93,35 +94,38 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
93
94
}
94
95
95
96
if ( options ?. refreshOptions ?. enabled ) {
96
- const { watchedSettings, refreshIntervalInMs } = options . refreshOptions ;
97
- // validate watched settings
98
- if ( watchedSettings === undefined || watchedSettings . length === 0 ) {
99
- throw new Error ( "Refresh is enabled but no watched settings are specified." ) ;
97
+ const { watchedSettings, refreshIntervalInMs, watchAll } = options . refreshOptions ;
98
+ // validate refresh options
99
+ if ( watchAll !== true ) {
100
+ if ( watchedSettings === undefined || watchedSettings . length === 0 ) {
101
+ throw new Error ( "Refresh is enabled but no watched settings are specified." ) ;
102
+ } else {
103
+ for ( const setting of watchedSettings ) {
104
+ if ( setting . key . includes ( "*" ) || setting . key . includes ( "," ) ) {
105
+ throw new Error ( "The characters '*' and ',' are not supported in key of watched settings." ) ;
106
+ }
107
+ if ( setting . label ?. includes ( "*" ) || setting . label ?. includes ( "," ) ) {
108
+ throw new Error ( "The characters '*' and ',' are not supported in label of watched settings." ) ;
109
+ }
110
+ this . #sentinels. push ( setting ) ;
111
+ }
112
+ }
113
+ } else if ( watchedSettings && watchedSettings . length > 0 ) {
114
+ throw new Error ( "Watched settings should not be specified when registerAll is enabled." ) ;
100
115
}
101
-
102
116
// custom refresh interval
103
117
if ( refreshIntervalInMs !== undefined ) {
104
118
if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
105
119
throw new Error ( `The refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
106
-
107
120
} else {
108
121
this . #refreshInterval = refreshIntervalInMs ;
109
122
}
110
123
}
111
-
112
- for ( const setting of watchedSettings ) {
113
- if ( setting . key . includes ( "*" ) || setting . key . includes ( "," ) ) {
114
- throw new Error ( "The characters '*' and ',' are not supported in key of watched settings." ) ;
115
- }
116
- if ( setting . label ?. includes ( "*" ) || setting . label ?. includes ( "," ) ) {
117
- throw new Error ( "The characters '*' and ',' are not supported in label of watched settings." ) ;
118
- }
119
- this . #sentinels. push ( setting ) ;
120
- }
121
-
122
124
this . #refreshTimer = new RefreshTimer ( this . #refreshInterval) ;
123
125
}
124
126
127
+ this . #keyValueSelectors = getValidKeyValueSelectors ( options ?. selectors ) ;
128
+
125
129
// feature flag options
126
130
if ( options ?. featureFlagOptions ?. enabled ) {
127
131
// validate feature flag selectors
@@ -184,6 +188,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
184
188
return ! ! this . #options?. refreshOptions ?. enabled ;
185
189
}
186
190
191
+ get #watchAll( ) : boolean {
192
+ return ! ! this . #options?. refreshOptions ?. watchAll ;
193
+ }
194
+
187
195
get #featureFlagEnabled( ) : boolean {
188
196
return ! ! this . #options?. featureFlagOptions ?. enabled ;
189
197
}
@@ -228,29 +236,42 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
228
236
throw new Error ( "All clients failed to get configuration settings." ) ;
229
237
}
230
238
231
- async #loadSelectedKeyValues( ) : Promise < ConfigurationSetting [ ] > {
232
- // validate selectors
233
- const selectors = getValidKeyValueSelectors ( this . #options?. selectors ) ;
234
-
239
+ async #loadConfigurationSettings( loadFeatureFlag : boolean = false ) : Promise < ConfigurationSetting [ ] > {
240
+ const selectors = loadFeatureFlag ? this . #featureFlagSelectors : this . #keyValueSelectors;
235
241
const funcToExecute = async ( client ) => {
236
242
const loadedSettings : ConfigurationSetting [ ] = [ ] ;
237
- for ( const selector of selectors ) {
243
+ // deep copy selectors to avoid modification if current client fails
244
+ const selectorsToUpdate = JSON . parse (
245
+ JSON . stringify ( selectors )
246
+ ) ;
247
+
248
+ for ( const selector of selectorsToUpdate ) {
238
249
const listOptions : ListConfigurationSettingsOptions = {
239
250
keyFilter : selector . keyFilter ,
240
251
labelFilter : selector . labelFilter
241
252
} ;
242
253
243
- const settings = listConfigurationSettingsWithTrace (
254
+ const pageEtags : string [ ] = [ ] ;
255
+ const pageIterator = listConfigurationSettingsWithTrace (
244
256
this . #requestTraceOptions,
245
257
client ,
246
258
listOptions
247
- ) ;
248
-
249
- for await ( const setting of settings ) {
250
- if ( ! isFeatureFlag ( setting ) ) { // exclude feature flags
251
- loadedSettings . push ( setting ) ;
259
+ ) . byPage ( ) ;
260
+ for await ( const page of pageIterator ) {
261
+ pageEtags . push ( page . etag ?? "" ) ;
262
+ for ( const setting of page . items ) {
263
+ if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
264
+ loadedSettings . push ( setting ) ;
265
+ }
252
266
}
253
267
}
268
+ selector . pageEtags = pageEtags ;
269
+ }
270
+
271
+ if ( loadFeatureFlag ) {
272
+ this . #featureFlagSelectors = selectorsToUpdate ;
273
+ } else {
274
+ this . #keyValueSelectors = selectorsToUpdate ;
254
275
}
255
276
return loadedSettings ;
256
277
} ;
@@ -262,10 +283,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
262
283
* Update etag of watched settings from loaded data. If a watched setting is not covered by any selector, a request will be sent to retrieve it.
263
284
*/
264
285
async #updateWatchedKeyValuesEtag( existingSettings : ConfigurationSetting [ ] ) : Promise < void > {
265
- if ( ! this . #refreshEnabled) {
266
- return ;
267
- }
268
-
269
286
for ( const sentinel of this . #sentinels) {
270
287
const matchedSetting = existingSettings . find ( s => s . key === sentinel . key && s . label === sentinel . label ) ;
271
288
if ( matchedSetting ) {
@@ -285,8 +302,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
285
302
286
303
async #loadSelectedAndWatchedKeyValues( ) {
287
304
const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
288
- const loadedSettings = await this . #loadSelectedKeyValues( ) ;
289
- await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
305
+ const loadedSettings = await this . #loadConfigurationSettings( ) ;
306
+ if ( this . #refreshEnabled && ! this . #watchAll) {
307
+ await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
308
+ }
290
309
291
310
// process key-values, watched settings have higher priority
292
311
for ( const setting of loadedSettings ) {
@@ -309,42 +328,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
309
328
}
310
329
311
330
async #loadFeatureFlags( ) {
312
- // Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
313
- const funcToExecute = async ( client ) => {
314
- const featureFlagSettings : ConfigurationSetting [ ] = [ ] ;
315
- // deep copy selectors to avoid modification if current client fails
316
- const selectors = JSON . parse (
317
- JSON . stringify ( this . #featureFlagSelectors)
318
- ) ;
319
-
320
- for ( const selector of selectors ) {
321
- const listOptions : ListConfigurationSettingsOptions = {
322
- keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
323
- labelFilter : selector . labelFilter
324
- } ;
325
-
326
- const pageEtags : string [ ] = [ ] ;
327
- const pageIterator = listConfigurationSettingsWithTrace (
328
- this . #requestTraceOptions,
329
- client ,
330
- listOptions
331
- ) . byPage ( ) ;
332
- for await ( const page of pageIterator ) {
333
- pageEtags . push ( page . etag ?? "" ) ;
334
- for ( const setting of page . items ) {
335
- if ( isFeatureFlag ( setting ) ) {
336
- featureFlagSettings . push ( setting ) ;
337
- }
338
- }
339
- }
340
- selector . pageEtags = pageEtags ;
341
- }
342
-
343
- this . #featureFlagSelectors = selectors ;
344
- return featureFlagSettings ;
345
- } ;
346
-
347
- const featureFlagSettings = await this . #executeWithFailoverPolicy( funcToExecute ) as ConfigurationSetting [ ] ;
331
+ const loadFeatureFlag = true ;
332
+ const featureFlagSettings = await this . #loadConfigurationSettings( loadFeatureFlag ) ;
348
333
349
334
// parse feature flags
350
335
const featureFlags = await Promise . all (
@@ -458,6 +443,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
458
443
459
444
// try refresh if any of watched settings is changed.
460
445
let needRefresh = false ;
446
+ if ( this . #watchAll) {
447
+ needRefresh = await this . #checkKeyValueCollectionChanged( this . #keyValueSelectors) ;
448
+ }
461
449
for ( const sentinel of this . #sentinels. values ( ) ) {
462
450
const response = await this . #getConfigurationSetting( sentinel , {
463
451
onlyIfChanged : true
@@ -490,11 +478,20 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
490
478
return Promise . resolve ( false ) ;
491
479
}
492
480
493
- // check if any feature flag is changed
481
+ const needRefresh = await this . #checkKeyValueCollectionChanged( this . #featureFlagSelectors) ;
482
+ if ( needRefresh ) {
483
+ await this . #loadFeatureFlags( ) ;
484
+ }
485
+
486
+ this . #featureFlagRefreshTimer. reset ( ) ;
487
+ return Promise . resolve ( needRefresh ) ;
488
+ }
489
+
490
+ async #checkKeyValueCollectionChanged( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
494
491
const funcToExecute = async ( client ) => {
495
- for ( const selector of this . #featureFlagSelectors ) {
492
+ for ( const selector of selectors ) {
496
493
const listOptions : ListConfigurationSettingsOptions = {
497
- keyFilter : ` ${ featureFlagPrefix } ${ selector . keyFilter } ` ,
494
+ keyFilter : selector . keyFilter ,
498
495
labelFilter : selector . labelFilter ,
499
496
pageEtags : selector . pageEtags
500
497
} ;
@@ -514,13 +511,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
514
511
return false ;
515
512
} ;
516
513
517
- const needRefresh : boolean = await this . #executeWithFailoverPolicy( funcToExecute ) ;
518
- if ( needRefresh ) {
519
- await this . #loadFeatureFlags( ) ;
520
- }
521
-
522
- this . #featureFlagRefreshTimer. reset ( ) ;
523
- return Promise . resolve ( needRefresh ) ;
514
+ const isChanged = await this . #executeWithFailoverPolicy( funcToExecute ) ;
515
+ return isChanged ;
524
516
}
525
517
526
518
onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
@@ -813,18 +805,21 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
813
805
}
814
806
815
807
function getValidKeyValueSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
816
- if ( ! selectors || selectors . length === 0 ) {
808
+ if ( selectors === undefined || selectors . length === 0 ) {
817
809
// Default selector: key: *, label: \0
818
810
return [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
819
811
}
820
812
return getValidSelectors ( selectors ) ;
821
813
}
822
814
823
815
function getValidFeatureFlagSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
824
- if ( ! selectors || selectors . length === 0 ) {
816
+ if ( selectors === undefined || selectors . length === 0 ) {
825
817
// selectors must be explicitly provided.
826
818
throw new Error ( "Feature flag selectors must be provided." ) ;
827
819
} else {
820
+ selectors . forEach ( selector => {
821
+ selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
822
+ } ) ;
828
823
return getValidSelectors ( selectors ) ;
829
824
}
830
825
}
0 commit comments