@@ -61,18 +61,14 @@ import {
6161} from "./requestTracing/utils.js" ;
6262import { FeatureFlagTracingOptions } from "./requestTracing/featureFlagTracingOptions.js" ;
6363import { AIConfigurationTracingOptions } from "./requestTracing/aiConfigurationTracingOptions.js" ;
64- import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
64+ import { KeyFilter , LabelFilter , SettingWatcher , SettingSelector , PagedSettingsWatcher , WatchedSetting } from "./types.js" ;
6565import { ConfigurationClientManager } from "./configurationClientManager.js" ;
6666import { getFixedBackoffDuration , getExponentialBackoffDuration } from "./common/backoffUtils.js" ;
6767import { InvalidOperationError , ArgumentError , isFailoverableError , isInputError } from "./common/errors.js" ;
6868import { ErrorMessages } from "./common/errorMessages.js" ;
6969
7070const MIN_DELAY_FOR_UNHANDLED_FAILURE = 5_000 ; // 5 seconds
7171
72- type PagedSettingSelector = SettingSelector & {
73- pageEtags ?: string [ ] ;
74- } ;
75-
7672export class AzureAppConfigurationImpl implements AzureAppConfiguration {
7773 /**
7874 * Hosting key-value pairs in the configuration store.
@@ -102,7 +98,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
10298 * Aka watched settings.
10399 */
104100 #refreshEnabled: boolean = false ;
105- #sentinels: ConfigurationSettingId [ ] = [ ] ;
101+ #sentinels: Map < WatchedSetting , SettingWatcher > = new Map ( ) ;
106102 #watchAll: boolean = false ;
107103 #kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
108104 #kvRefreshTimer: RefreshTimer ;
@@ -122,11 +118,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
122118 /**
123119 * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
124120 */
125- #kvSelectors: PagedSettingSelector [ ] = [ ] ;
121+ #kvSelectors: PagedSettingsWatcher [ ] = [ ] ;
126122 /**
127123 * Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
128124 */
129- #ffSelectors: PagedSettingSelector [ ] = [ ] ;
125+ #ffSelectors: PagedSettingsWatcher [ ] = [ ] ;
130126
131127 // Load balancing
132128 #lastSuccessfulEndpoint: string = "" ;
@@ -165,7 +161,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
165161 if ( setting . label ?. includes ( "*" ) || setting . label ?. includes ( "," ) ) {
166162 throw new ArgumentError ( ErrorMessages . INVALID_WATCHED_SETTINGS_LABEL ) ;
167163 }
168- this . #sentinels. push ( setting ) ;
164+ this . #sentinels. set ( setting , { etag : undefined } ) ;
169165 }
170166 }
171167
@@ -394,7 +390,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
394390 let postAttempts = 0 ;
395391 do { // at least try to load once
396392 try {
397- await this . #loadSelectedAndWatchedKeyValues( ) ;
393+ if ( this . #refreshEnabled && ! this . #watchAll) {
394+ await this . #loadWatchedSettings( ) ;
395+ }
396+
397+ await this . #loadSelectedKeyValues( ) ;
398+
398399 if ( this . #featureFlagEnabled) {
399400 await this . #loadFeatureFlags( ) ;
400401 }
@@ -494,7 +495,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
494495 // the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
495496 const loadedSettings : Map < string , ConfigurationSetting > = new Map < string , ConfigurationSetting > ( ) ;
496497 // deep copy selectors to avoid modification if current client fails
497- const selectorsToUpdate = JSON . parse (
498+ const selectorsToUpdate : PagedSettingsWatcher [ ] = JSON . parse (
498499 JSON . stringify ( selectors )
499500 ) ;
500501
@@ -505,22 +506,22 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
505506 labelFilter : selector . labelFilter ,
506507 tagsFilter : selector . tagFilters
507508 } ;
508- const pageEtags : string [ ] = [ ] ;
509+ const pageWatchers : SettingWatcher [ ] = [ ] ;
509510 const pageIterator = listConfigurationSettingsWithTrace (
510511 this . #requestTraceOptions,
511512 client ,
512513 listOptions
513514 ) . byPage ( ) ;
514515
515516 for await ( const page of pageIterator ) {
516- pageEtags . push ( page . etag ?? "" ) ;
517+ pageWatchers . push ( { etag : page . etag } ) ;
517518 for ( const setting of page . items ) {
518519 if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
519520 loadedSettings . set ( setting . key , setting ) ;
520521 }
521522 }
522523 }
523- selector . pageEtags = pageEtags ;
524+ selector . pageWatchers = pageWatchers ;
524525 } else { // snapshot selector
525526 const snapshot = await this . #getSnapshot( selector . snapshotName ) ;
526527 if ( snapshot === undefined ) {
@@ -557,15 +558,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
557558 }
558559
559560 /**
560- * Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
561+ * Loads selected key-values from App Configuration to the local configuration.
561562 */
562- async #loadSelectedAndWatchedKeyValues ( ) {
563+ async #loadSelectedKeyValues ( ) {
563564 this . #secretReferences = [ ] ; // clear all cached key vault reference configuration settings
564565 const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
565566 const loadedSettings : ConfigurationSetting [ ] = await this . #loadConfigurationSettings( ) ;
566- if ( this . #refreshEnabled && ! this . #watchAll) {
567- await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
568- }
569567
570568 if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
571569 // reset old AI configuration tracing in order to track the information present in the current response from server
@@ -595,22 +593,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
595593 }
596594
597595 /**
598- * Updates 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 .
596+ * Loads watched settings (sentinels) for refresh from App Configuration to the local configuration .
599597 */
600- async #updateWatchedKeyValuesEtag( existingSettings : ConfigurationSetting [ ] ) : Promise < void > {
601- const updatedSentinels : ConfigurationSettingId [ ] = [ ] ;
602- for ( const sentinel of this . #sentinels) {
603- const matchedSetting = existingSettings . find ( s => s . key === sentinel . key && s . label === sentinel . label ) ;
604- if ( matchedSetting ) {
605- updatedSentinels . push ( { ...sentinel , etag : matchedSetting . etag } ) ;
606- } else {
607- // Send a request to retrieve key-value since it may be either not loaded or loaded with a different label or different casing
608- const { key, label } = sentinel ;
609- const response = await this . #getConfigurationSetting( { key, label } ) ;
610- updatedSentinels . push ( { ...sentinel , etag : response ?. etag } ) ;
611- }
598+ async #loadWatchedSettings( ) : Promise < void > {
599+ for ( const watchedSetting of this . #sentinels. keys ( ) ) {
600+ const configurationSettingId : ConfigurationSettingId = { key : watchedSetting . key , label : watchedSetting . label } ;
601+ const response = await this . #getConfigurationSetting( configurationSettingId , { onlyIfChanged : false } ) ;
602+ this . #sentinels. set ( watchedSetting , { etag : response ?. etag } ) ;
612603 }
613- this . #sentinels = updatedSentinels ;
614604 }
615605
616606 /**
@@ -657,27 +647,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
657647
658648 // try refresh if any of watched settings is changed.
659649 let needRefresh = false ;
650+ let changedSentinel ;
651+ let changedSentinelWatcher ;
660652 if ( this . #watchAll) {
661653 needRefresh = await this . #checkConfigurationSettingsChange( this . #kvSelectors) ;
662- }
663- for ( const sentinel of this . #sentinels. values ( ) ) {
664- const response = await this . #getConfigurationSetting( sentinel , {
665- onlyIfChanged : true
666- } ) ;
667-
668- if ( response ?. statusCode === 200 // created or changed
669- || ( response === undefined && sentinel . etag !== undefined ) // deleted
670- ) {
671- needRefresh = true ;
672- break ;
654+ } else {
655+ for ( const watchedSetting of this . #sentinels. keys ( ) ) {
656+ const configurationSettingId : ConfigurationSettingId = { key : watchedSetting . key , label : watchedSetting . label , etag : this . #sentinels. get ( watchedSetting ) ?. etag } ;
657+ const response = await this . #getConfigurationSetting( configurationSettingId , {
658+ onlyIfChanged : true
659+ } ) ;
660+
661+ const watcher = this . #sentinels. get ( watchedSetting ) ;
662+ if ( response ?. statusCode === 200 // created or changed
663+ || ( response === undefined && watcher ?. etag !== undefined ) // deleted
664+ ) {
665+ changedSentinel = watchedSetting ;
666+ changedSentinelWatcher = watcher ;
667+ needRefresh = true ;
668+ break ;
669+ }
673670 }
674671 }
675672
676673 if ( needRefresh ) {
677674 for ( const adapter of this . #adapters) {
678675 await adapter . onChangeDetected ( ) ;
679676 }
680- await this . #loadSelectedAndWatchedKeyValues( ) ;
677+ await this . #loadSelectedKeyValues( ) ;
678+ this . #sentinels. set ( changedSentinel , changedSentinelWatcher ) ; // update the changed sentinel's watcher
681679 }
682680
683681 this . #kvRefreshTimer. reset ( ) ;
@@ -727,17 +725,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
727725 * @param selectors - The @see PagedSettingSelector of the kev-value collection.
728726 * @returns true if key-value collection has changed, false otherwise.
729727 */
730- async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
728+ async #checkConfigurationSettingsChange( selectors : PagedSettingsWatcher [ ] ) : Promise < boolean > {
731729 const funcToExecute = async ( client ) => {
732730 for ( const selector of selectors ) {
733731 if ( selector . snapshotName ) { // skip snapshot selector
734732 continue ;
735733 }
734+ const pageWatchers : SettingWatcher [ ] = selector . pageWatchers ?? [ ] ;
736735 const listOptions : ListConfigurationSettingsOptions = {
737736 keyFilter : selector . keyFilter ,
738737 labelFilter : selector . labelFilter ,
739738 tagsFilter : selector . tagFilters ,
740- pageEtags : selector . pageEtags
739+ pageEtags : pageWatchers . map ( w => w . etag ?? "" )
741740 } ;
742741
743742 const pageIterator = listConfigurationSettingsWithTrace (
@@ -747,6 +746,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
747746 ) . byPage ( ) ;
748747
749748 for await ( const page of pageIterator ) {
749+ // when conditional request is sent, the response will be 304 if not changed
750750 if ( page . _response . status === 200 ) { // created or changed
751751 return true ;
752752 }
0 commit comments