@@ -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