@@ -76,6 +76,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
7676 #featureFlagRefreshTimer: RefreshTimer ;
7777
7878 // selectors
79+ #keyValueSelectors: PagedSettingSelector [ ] = [ ] ;
7980 #featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
8081
8182 constructor (
@@ -93,35 +94,38 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
9394 }
9495
9596 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." ) ;
100115 }
101-
102116 // custom refresh interval
103117 if ( refreshIntervalInMs !== undefined ) {
104118 if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
105119 throw new Error ( `The refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
106-
107120 } else {
108121 this . #refreshInterval = refreshIntervalInMs ;
109122 }
110123 }
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-
122124 this . #refreshTimer = new RefreshTimer ( this . #refreshInterval) ;
123125 }
124126
127+ this . #keyValueSelectors = getValidKeyValueSelectors ( options ?. selectors ) ;
128+
125129 // feature flag options
126130 if ( options ?. featureFlagOptions ?. enabled ) {
127131 // validate feature flag selectors
@@ -184,6 +188,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
184188 return ! ! this . #options?. refreshOptions ?. enabled ;
185189 }
186190
191+ get #watchAll( ) : boolean {
192+ return ! ! this . #options?. refreshOptions ?. watchAll ;
193+ }
194+
187195 get #featureFlagEnabled( ) : boolean {
188196 return ! ! this . #options?. featureFlagOptions ?. enabled ;
189197 }
@@ -228,29 +236,42 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
228236 throw new Error ( "All clients failed to get configuration settings." ) ;
229237 }
230238
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;
235241 const funcToExecute = async ( client ) => {
236242 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 ) {
238249 const listOptions : ListConfigurationSettingsOptions = {
239250 keyFilter : selector . keyFilter ,
240251 labelFilter : selector . labelFilter
241252 } ;
242253
243- const settings = listConfigurationSettingsWithTrace (
254+ const pageEtags : string [ ] = [ ] ;
255+ const pageIterator = listConfigurationSettingsWithTrace (
244256 this . #requestTraceOptions,
245257 client ,
246258 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+ }
252266 }
253267 }
268+ selector . pageEtags = pageEtags ;
269+ }
270+
271+ if ( loadFeatureFlag ) {
272+ this . #featureFlagSelectors = selectorsToUpdate ;
273+ } else {
274+ this . #keyValueSelectors = selectorsToUpdate ;
254275 }
255276 return loadedSettings ;
256277 } ;
@@ -262,10 +283,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
262283 * 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.
263284 */
264285 async #updateWatchedKeyValuesEtag( existingSettings : ConfigurationSetting [ ] ) : Promise < void > {
265- if ( ! this . #refreshEnabled) {
266- return ;
267- }
268-
269286 for ( const sentinel of this . #sentinels) {
270287 const matchedSetting = existingSettings . find ( s => s . key === sentinel . key && s . label === sentinel . label ) ;
271288 if ( matchedSetting ) {
@@ -285,8 +302,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
285302
286303 async #loadSelectedAndWatchedKeyValues( ) {
287304 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+ }
290309
291310 // process key-values, watched settings have higher priority
292311 for ( const setting of loadedSettings ) {
@@ -309,42 +328,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
309328 }
310329
311330 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 ) ;
348333
349334 // parse feature flags
350335 const featureFlags = await Promise . all (
@@ -458,6 +443,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
458443
459444 // try refresh if any of watched settings is changed.
460445 let needRefresh = false ;
446+ if ( this . #watchAll) {
447+ needRefresh = await this . #checkKeyValueCollectionChanged( this . #keyValueSelectors) ;
448+ }
461449 for ( const sentinel of this . #sentinels. values ( ) ) {
462450 const response = await this . #getConfigurationSetting( sentinel , {
463451 onlyIfChanged : true
@@ -490,11 +478,20 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
490478 return Promise . resolve ( false ) ;
491479 }
492480
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 > {
494491 const funcToExecute = async ( client ) => {
495- for ( const selector of this . #featureFlagSelectors ) {
492+ for ( const selector of selectors ) {
496493 const listOptions : ListConfigurationSettingsOptions = {
497- keyFilter : ` ${ featureFlagPrefix } ${ selector . keyFilter } ` ,
494+ keyFilter : selector . keyFilter ,
498495 labelFilter : selector . labelFilter ,
499496 pageEtags : selector . pageEtags
500497 } ;
@@ -514,13 +511,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
514511 return false ;
515512 } ;
516513
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 ;
524516 }
525517
526518 onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
@@ -813,18 +805,21 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
813805}
814806
815807function getValidKeyValueSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
816- if ( ! selectors || selectors . length === 0 ) {
808+ if ( selectors === undefined || selectors . length === 0 ) {
817809 // Default selector: key: *, label: \0
818810 return [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
819811 }
820812 return getValidSelectors ( selectors ) ;
821813}
822814
823815function getValidFeatureFlagSelectors ( selectors ?: SettingSelector [ ] ) : SettingSelector [ ] {
824- if ( ! selectors || selectors . length === 0 ) {
816+ if ( selectors === undefined || selectors . length === 0 ) {
825817 // selectors must be explicitly provided.
826818 throw new Error ( "Feature flag selectors must be provided." ) ;
827819 } else {
820+ selectors . forEach ( selector => {
821+ selector . keyFilter = `${ featureFlagPrefix } ${ selector . keyFilter } ` ;
822+ } ) ;
828823 return getValidSelectors ( selectors ) ;
829824 }
830825}
0 commit comments