@@ -15,6 +15,13 @@ import { RefreshTimer } from "./refresh/RefreshTimer";
15
15
import { getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils" ;
16
16
import { KeyFilter , LabelFilter , SettingSelector } from "./types" ;
17
17
18
+ type PagedSettingSelector = SettingSelector & {
19
+ /**
20
+ * Key: page eTag, Value: feature flag configurations
21
+ */
22
+ pageEtags ?: string [ ] ;
23
+ } ;
24
+
18
25
export class AzureAppConfigurationImpl implements AzureAppConfiguration {
19
26
/**
20
27
* Hosting key-value pairs in the configuration store.
@@ -45,6 +52,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
45
52
#featureFlagRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
46
53
#featureFlagRefreshTimer: RefreshTimer ;
47
54
55
+ // selectors
56
+ #featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
57
+
48
58
constructor (
49
59
client : AppConfigurationClient ,
50
60
options : AzureAppConfigurationOptions | undefined
@@ -90,19 +100,23 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
90
100
}
91
101
92
102
// 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
+ }
102
116
}
103
- }
104
117
105
- this . #featureFlagRefreshTimer = new RefreshTimer ( this . #featureFlagRefreshInterval) ;
118
+ this . #featureFlagRefreshTimer = new RefreshTimer ( this . #featureFlagRefreshInterval) ;
119
+ }
106
120
}
107
121
108
122
this . #adapters. push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions ) ) ;
@@ -233,7 +247,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
233
247
}
234
248
235
249
async #clearLoadedKeyValues( ) {
236
- for ( const key of this . #configMap. keys ( ) ) {
250
+ for ( const key of this . #configMap. keys ( ) ) {
237
251
if ( key !== FEATURE_MANAGEMENT_KEY_NAME ) {
238
252
this . #configMap. delete ( key ) ;
239
253
}
@@ -243,22 +257,31 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
243
257
async #loadFeatureFlags( ) {
244
258
// Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
245
259
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) {
248
261
const listOptions : ListConfigurationSettingsOptions = {
249
262
keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
250
263
labelFilter : selector . labelFilter
251
264
} ;
252
- const settings = listConfigurationSettingsWithTrace (
265
+
266
+ const pageEtags : string [ ] = [ ] ;
267
+ const pageIterator = listConfigurationSettingsWithTrace (
253
268
this . #requestTraceOptions,
254
269
this . #client,
255
270
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
+ }
260
282
}
261
283
}
284
+ selector . pageEtags = pageEtags ;
262
285
}
263
286
264
287
// parse feature flags
@@ -410,16 +433,41 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
410
433
return Promise . resolve ( false ) ;
411
434
}
412
435
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
+ }
421
456
}
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 ) ;
423
471
}
424
472
425
473
onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
0 commit comments