@@ -15,6 +15,13 @@ import { RefreshTimer } from "./refresh/RefreshTimer";
1515import { getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils" ;
1616import { KeyFilter , LabelFilter , SettingSelector } from "./types" ;
1717
18+ type PagedSettingSelector = SettingSelector & {
19+ /**
20+ * Key: page eTag, Value: feature flag configurations
21+ */
22+ pageEtags ?: string [ ] ;
23+ } ;
24+
1825export class AzureAppConfigurationImpl implements AzureAppConfiguration {
1926 /**
2027 * Hosting key-value pairs in the configuration store.
@@ -45,6 +52,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
4552 #featureFlagRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
4653 #featureFlagRefreshTimer: RefreshTimer ;
4754
55+ // selectors
56+ #featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
57+
4858 constructor (
4959 client : AppConfigurationClient ,
5060 options : AzureAppConfigurationOptions | undefined
@@ -90,19 +100,23 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
90100 }
91101
92102 // feature flag options
93- if ( options ?. featureFlagOptions ?. enabled && options . featureFlagOptions . refresh ?. enabled ) {
94- const { refreshIntervalInMs } = options . featureFlagOptions . refresh ;
95-
96- // custom refresh interval
97- if ( refreshIntervalInMs !== undefined ) {
98- if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
99- throw new Error ( `The feature flag refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
100- } else {
101- this . #featureFlagRefreshInterval = refreshIntervalInMs ;
103+ if ( options ?. featureFlagOptions ?. enabled ) {
104+ // validate feature flag selectors
105+ this . #featureFlagSelectors = getValidFeatureFlagSelectors ( options . featureFlagOptions . selectors ) ;
106+
107+ if ( options . featureFlagOptions . refresh ?. enabled ) {
108+ const { refreshIntervalInMs } = options . featureFlagOptions . refresh ;
109+ // custom refresh interval
110+ if ( refreshIntervalInMs !== undefined ) {
111+ if ( refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS ) {
112+ throw new Error ( `The feature flag refresh interval cannot be less than ${ MIN_REFRESH_INTERVAL_IN_MS } milliseconds.` ) ;
113+ } else {
114+ this . #featureFlagRefreshInterval = refreshIntervalInMs ;
115+ }
102116 }
103- }
104117
105- this . #featureFlagRefreshTimer = new RefreshTimer ( this . #featureFlagRefreshInterval) ;
118+ this . #featureFlagRefreshTimer = new RefreshTimer ( this . #featureFlagRefreshInterval) ;
119+ }
106120 }
107121
108122 this . #adapters. push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions ) ) ;
@@ -233,7 +247,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
233247 }
234248
235249 async #clearLoadedKeyValues( ) {
236- for ( const key of this . #configMap. keys ( ) ) {
250+ for ( const key of this . #configMap. keys ( ) ) {
237251 if ( key !== FEATURE_MANAGEMENT_KEY_NAME ) {
238252 this . #configMap. delete ( key ) ;
239253 }
@@ -243,22 +257,31 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
243257 async #loadFeatureFlags( ) {
244258 // Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
245259 const featureFlagsMap = new Map < string , any > ( ) ;
246- const featureFlagSelectors = getValidFeatureFlagSelectors ( this . #options?. featureFlagOptions ?. selectors ) ;
247- for ( const selector of featureFlagSelectors ) {
260+ for ( const selector of this . #featureFlagSelectors) {
248261 const listOptions : ListConfigurationSettingsOptions = {
249262 keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
250263 labelFilter : selector . labelFilter
251264 } ;
252- const settings = listConfigurationSettingsWithTrace (
265+
266+ const pageEtags : string [ ] = [ ] ;
267+ const pageIterator = listConfigurationSettingsWithTrace (
253268 this . #requestTraceOptions,
254269 this . #client,
255270 listOptions
256- ) ;
257- for await ( const setting of settings ) {
258- if ( isFeatureFlag ( setting ) ) {
259- featureFlagsMap . set ( setting . key , setting . value ) ;
271+ ) . byPage ( ) ;
272+ for await ( const page of pageIterator ) {
273+ if ( page . _response . status === 200 ) {
274+ if ( page . etag ) {
275+ pageEtags . push ( page . etag ) ;
276+ }
277+ }
278+ for ( const setting of page . items ) {
279+ if ( isFeatureFlag ( setting ) ) {
280+ featureFlagsMap . set ( setting . key , setting . value ) ;
281+ }
260282 }
261283 }
284+ selector . pageEtags = pageEtags ;
262285 }
263286
264287 // parse feature flags
@@ -410,16 +433,41 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
410433 return Promise . resolve ( false ) ;
411434 }
412435
413- try {
414- // TODO: instead of refreshing all feature flags, only refresh the changed ones with etag
415- await this . #loadFeatureFlags( ) ;
416- this . #featureFlagRefreshTimer. reset ( ) ;
417- } catch ( error ) {
418- // if refresh failed, backoff
419- this . #featureFlagRefreshTimer. backoff ( ) ;
420- throw error ;
436+ // check if any feature flag is changed
437+ let needRefresh = false ;
438+ for ( const selector of this . #featureFlagSelectors) {
439+ const listOptions : ListConfigurationSettingsOptions = {
440+ keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
441+ labelFilter : selector . labelFilter ,
442+ pageEtags : selector . pageEtags
443+ } ;
444+ const pageIterator = listConfigurationSettingsWithTrace (
445+ this . #requestTraceOptions,
446+ this . #client,
447+ listOptions
448+ ) . byPage ( ) ;
449+ for await ( const page of pageIterator ) {
450+ if ( page . _response . status === 200 ) { // created or changed
451+ needRefresh = true ;
452+ break ;
453+ }
454+ // TODO: handle page deleted?
455+ }
421456 }
422- return Promise . resolve ( true ) ;
457+
458+ if ( needRefresh ) {
459+ try {
460+ await this . #loadFeatureFlags( ) ;
461+ this . #featureFlagRefreshTimer. reset ( ) ;
462+ } catch ( error ) {
463+ // if refresh failed, backoff
464+ this . #featureFlagRefreshTimer. backoff ( ) ;
465+ throw error ;
466+ }
467+ return Promise . resolve ( true ) ;
468+ }
469+
470+ return Promise . resolve ( false ) ;
423471 }
424472
425473 onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
0 commit comments