@@ -53,18 +53,14 @@ import {
5353} from "./requestTracing/utils.js" ;
5454import { FeatureFlagTracingOptions } from "./requestTracing/featureFlagTracingOptions.js" ;
5555import { AIConfigurationTracingOptions } from "./requestTracing/aiConfigurationTracingOptions.js" ;
56- import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
56+ import { KeyFilter , LabelFilter , SettingWatcher , SettingSelector , PagedSettingsWatcher , WatchedSetting } from "./types.js" ;
5757import { ConfigurationClientManager } from "./configurationClientManager.js" ;
5858import { getFixedBackoffDuration , getExponentialBackoffDuration } from "./common/backoffUtils.js" ;
5959import { InvalidOperationError , ArgumentError , isFailoverableError , isInputError } from "./common/errors.js" ;
6060import { ErrorMessages } from "./common/errorMessages.js" ;
6161
6262const MIN_DELAY_FOR_UNHANDLED_FAILURE = 5_000 ; // 5 seconds
6363
64- type PagedSettingSelector = SettingSelector & {
65- pageEtags ?: string [ ] ;
66- } ;
67-
6864export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6965 /**
7066 * Hosting key-value pairs in the configuration store.
@@ -94,7 +90,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
9490 * Aka watched settings.
9591 */
9692 #refreshEnabled: boolean = false ;
97- #sentinels: ConfigurationSettingId [ ] = [ ] ;
93+ #sentinels: Map < WatchedSetting , SettingWatcher > = new Map ( ) ;
9894 #watchAll: boolean = false ;
9995 #kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
10096 #kvRefreshTimer: RefreshTimer ;
@@ -114,11 +110,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
114110 /**
115111 * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
116112 */
117- #kvSelectors: PagedSettingSelector [ ] = [ ] ;
113+ #kvSelectors: PagedSettingsWatcher [ ] = [ ] ;
118114 /**
119115 * Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
120116 */
121- #ffSelectors: PagedSettingSelector [ ] = [ ] ;
117+ #ffSelectors: PagedSettingsWatcher [ ] = [ ] ;
122118
123119 // Load balancing
124120 #lastSuccessfulEndpoint: string = "" ;
@@ -157,7 +153,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
157153 if ( setting . label ?. includes ( "*" ) || setting . label ?. includes ( "," ) ) {
158154 throw new ArgumentError ( ErrorMessages . INVALID_WATCHED_SETTINGS_LABEL ) ;
159155 }
160- this . #sentinels. push ( setting ) ;
156+ this . #sentinels. set ( setting , { etag : undefined } ) ;
161157 }
162158 }
163159
@@ -386,7 +382,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
386382 let postAttempts = 0 ;
387383 do { // at least try to load once
388384 try {
389- await this . #loadSelectedAndWatchedKeyValues( ) ;
385+ if ( this . #refreshEnabled && ! this . #watchAll) {
386+ await this . #loadWatchedSettings( ) ;
387+ }
388+
389+ await this . #loadSelectedKeyValues( ) ;
390+
390391 if ( this . #featureFlagEnabled) {
391392 await this . #loadFeatureFlags( ) ;
392393 }
@@ -486,7 +487,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
486487 // the configuration setting loaded by the later selector in the iteration order will override the one from the earlier selector.
487488 const loadedSettings : Map < string , ConfigurationSetting > = new Map < string , ConfigurationSetting > ( ) ;
488489 // deep copy selectors to avoid modification if current client fails
489- const selectorsToUpdate = JSON . parse (
490+ const selectorsToUpdate : PagedSettingsWatcher [ ] = JSON . parse (
490491 JSON . stringify ( selectors )
491492 ) ;
492493
@@ -497,22 +498,22 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
497498 labelFilter : selector . labelFilter ,
498499 tagsFilter : selector . tagFilters
499500 } ;
500- const pageEtags : string [ ] = [ ] ;
501+ const pageWatchers : SettingWatcher [ ] = [ ] ;
501502 const pageIterator = listConfigurationSettingsWithTrace (
502503 this . #requestTraceOptions,
503504 client ,
504505 listOptions
505506 ) . byPage ( ) ;
506507
507508 for await ( const page of pageIterator ) {
508- pageEtags . push ( page . etag ?? "" ) ;
509+ pageWatchers . push ( { etag : page . etag } ) ;
509510 for ( const setting of page . items ) {
510511 if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
511512 loadedSettings . set ( setting . key , setting ) ;
512513 }
513514 }
514515 }
515- selector . pageEtags = pageEtags ;
516+ selector . pageWatchers = pageWatchers ;
516517 } else { // snapshot selector
517518 const snapshot = await this . #getSnapshot( selector . snapshotName ) ;
518519 if ( snapshot === undefined ) {
@@ -549,15 +550,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
549550 }
550551
551552 /**
552- * Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
553+ * Loads selected key-values from App Configuration to the local configuration.
553554 */
554- async #loadSelectedAndWatchedKeyValues ( ) {
555+ async #loadSelectedKeyValues ( ) {
555556 this . #secretReferences = [ ] ; // clear all cached key vault reference configuration settings
556557 const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
557558 const loadedSettings : ConfigurationSetting [ ] = await this . #loadConfigurationSettings( ) ;
558- if ( this . #refreshEnabled && ! this . #watchAll) {
559- await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
560- }
561559
562560 if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
563561 // reset old AI configuration tracing in order to track the information present in the current response from server
@@ -587,22 +585,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
587585 }
588586
589587 /**
590- * 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 .
588+ * Loads watched settings (sentinels) for refresh from App Configuration to the local configuration .
591589 */
592- async #updateWatchedKeyValuesEtag( existingSettings : ConfigurationSetting [ ] ) : Promise < void > {
593- const updatedSentinels : ConfigurationSettingId [ ] = [ ] ;
594- for ( const sentinel of this . #sentinels) {
595- const matchedSetting = existingSettings . find ( s => s . key === sentinel . key && s . label === sentinel . label ) ;
596- if ( matchedSetting ) {
597- updatedSentinels . push ( { ...sentinel , etag : matchedSetting . etag } ) ;
598- } else {
599- // Send a request to retrieve key-value since it may be either not loaded or loaded with a different label or different casing
600- const { key, label } = sentinel ;
601- const response = await this . #getConfigurationSetting( { key, label } ) ;
602- updatedSentinels . push ( { ...sentinel , etag : response ?. etag } ) ;
603- }
590+ async #loadWatchedSettings( ) : Promise < void > {
591+ for ( const watchedSetting of this . #sentinels. keys ( ) ) {
592+ const configurationSettingId : ConfigurationSettingId = { key : watchedSetting . key , label : watchedSetting . label } ;
593+ const response = await this . #getConfigurationSetting( configurationSettingId , { onlyIfChanged : false } ) ;
594+ this . #sentinels. set ( watchedSetting , { etag : response ?. etag } ) ;
604595 }
605- this . #sentinels = updatedSentinels ;
606596 }
607597
608598 /**
@@ -649,27 +639,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
649639
650640 // try refresh if any of watched settings is changed.
651641 let needRefresh = false ;
642+ let changedSentinel ;
643+ let changedSentinelWatcher ;
652644 if ( this . #watchAll) {
653645 needRefresh = await this . #checkConfigurationSettingsChange( this . #kvSelectors) ;
654- }
655- for ( const sentinel of this . #sentinels. values ( ) ) {
656- const response = await this . #getConfigurationSetting( sentinel , {
657- onlyIfChanged : true
658- } ) ;
659-
660- if ( response ?. statusCode === 200 // created or changed
661- || ( response === undefined && sentinel . etag !== undefined ) // deleted
662- ) {
663- needRefresh = true ;
664- break ;
646+ } else {
647+ for ( const watchedSetting of this . #sentinels. keys ( ) ) {
648+ const configurationSettingId : ConfigurationSettingId = { key : watchedSetting . key , label : watchedSetting . label , etag : this . #sentinels. get ( watchedSetting ) ?. etag } ;
649+ const response = await this . #getConfigurationSetting( configurationSettingId , {
650+ onlyIfChanged : true
651+ } ) ;
652+
653+ const watcher = this . #sentinels. get ( watchedSetting ) ;
654+ if ( response ?. statusCode === 200 // created or changed
655+ || ( response === undefined && watcher ?. etag !== undefined ) // deleted
656+ ) {
657+ changedSentinel = watchedSetting ;
658+ changedSentinelWatcher = watcher ;
659+ needRefresh = true ;
660+ break ;
661+ }
665662 }
666663 }
667664
668665 if ( needRefresh ) {
669666 for ( const adapter of this . #adapters) {
670667 await adapter . onChangeDetected ( ) ;
671668 }
672- await this . #loadSelectedAndWatchedKeyValues( ) ;
669+ await this . #loadSelectedKeyValues( ) ;
670+ this . #sentinels. set ( changedSentinel , changedSentinelWatcher ) ; // update the changed sentinel's watcher
673671 }
674672
675673 this . #kvRefreshTimer. reset ( ) ;
@@ -719,17 +717,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
719717 * @param selectors - The @see PagedSettingSelector of the kev-value collection.
720718 * @returns true if key-value collection has changed, false otherwise.
721719 */
722- async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
720+ async #checkConfigurationSettingsChange( selectors : PagedSettingsWatcher [ ] ) : Promise < boolean > {
723721 const funcToExecute = async ( client ) => {
724722 for ( const selector of selectors ) {
725723 if ( selector . snapshotName ) { // skip snapshot selector
726724 continue ;
727725 }
726+ const pageWatchers : SettingWatcher [ ] = selector . pageWatchers ?? [ ] ;
728727 const listOptions : ListConfigurationSettingsOptions = {
729728 keyFilter : selector . keyFilter ,
730729 labelFilter : selector . labelFilter ,
731730 tagsFilter : selector . tagFilters ,
732- pageEtags : selector . pageEtags
731+ pageEtags : pageWatchers . map ( w => w . etag ?? "" )
733732 } ;
734733
735734 const pageIterator = listConfigurationSettingsWithTrace (
@@ -739,6 +738,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
739738 ) . byPage ( ) ;
740739
741740 for await ( const page of pageIterator ) {
741+ // when conditional request is sent, the response will be 304 if not changed
742742 if ( page . _response . status === 200 ) { // created or changed
743743 return true ;
744744 }
0 commit comments