Skip to content

Commit 3c73598

Browse files
refresh option registerAll
1 parent a251f82 commit 3c73598

File tree

2 files changed

+86
-38
lines changed

2 files changed

+86
-38
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
7575
#featureFlagRefreshTimer: RefreshTimer;
7676

7777
// selectors
78+
#keyValueSelectors: PagedSettingSelector[] = [];
7879
#featureFlagSelectors: PagedSettingSelector[] = [];
7980

8081
constructor(
@@ -94,9 +95,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
9495
}
9596

9697
if (options?.refreshOptions?.enabled) {
97-
const { watchedSettings, refreshIntervalInMs } = options.refreshOptions;
98-
// validate watched settings
99-
if (watchedSettings === undefined || watchedSettings.length === 0) {
98+
const { watchedSettings, refreshIntervalInMs, registerAll } = options.refreshOptions;
99+
100+
// validate refresh options
101+
if (registerAll !== true && (watchedSettings === undefined || watchedSettings.length === 0)) {
100102
throw new Error("Refresh is enabled but no watched settings are specified.");
101103
}
102104

@@ -110,19 +112,30 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
110112
}
111113
}
112114

113-
for (const setting of watchedSettings) {
114-
if (setting.key.includes("*") || setting.key.includes(",")) {
115-
throw new Error("The characters '*' and ',' are not supported in key of watched settings.");
115+
if (registerAll !== true) {
116+
if (watchedSettings === undefined || watchedSettings.length === 0) {
117+
throw new Error("Refresh is enabled but no watched settings are specified.");
116118
}
117-
if (setting.label?.includes("*") || setting.label?.includes(",")) {
118-
throw new Error("The characters '*' and ',' are not supported in label of watched settings.");
119+
else {
120+
for (const setting of watchedSettings) {
121+
if (setting.key.includes("*") || setting.key.includes(",")) {
122+
throw new Error("The characters '*' and ',' are not supported in key of watched settings.");
123+
}
124+
if (setting.label?.includes("*") || setting.label?.includes(",")) {
125+
throw new Error("The characters '*' and ',' are not supported in label of watched settings.");
126+
}
127+
this.#sentinels.push(setting);
128+
}
119129
}
120-
this.#sentinels.push(setting);
130+
} else if (watchedSettings && watchedSettings.length > 0) {
131+
throw new Error("Watched settings should not be specified when registerAll is enabled.");
121132
}
122133

123134
this.#refreshTimer = new RefreshTimer(this.#refreshInterval);
124135
}
125136

137+
this.#keyValueSelectors = getValidKeyValueSelectors(options?.selectors);
138+
126139
// feature flag options
127140
if (options?.featureFlagOptions?.enabled) {
128141
// validate feature flag selectors
@@ -185,6 +198,10 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
185198
return !!this.#options?.refreshOptions?.enabled;
186199
}
187200

201+
get #registerAll(): boolean {
202+
return !!this.#options?.refreshOptions?.registerAll;
203+
}
204+
188205
get #featureFlagEnabled(): boolean {
189206
return !!this.#options?.featureFlagOptions?.enabled;
190207
}
@@ -204,26 +221,27 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
204221
async #loadSelectedKeyValues(): Promise<ConfigurationSetting[]> {
205222
const loadedSettings: ConfigurationSetting[] = [];
206223

207-
// validate selectors
208-
const selectors = getValidKeyValueSelectors(this.#options?.selectors);
209-
210-
for (const selector of selectors) {
224+
for (const selector of this.#keyValueSelectors) {
211225
const listOptions: ListConfigurationSettingsOptions = {
212226
keyFilter: selector.keyFilter,
213227
labelFilter: selector.labelFilter
214228
};
215229

216-
const settings = listConfigurationSettingsWithTrace(
230+
const pageEtags: string[] = [];
231+
const pageIterator = listConfigurationSettingsWithTrace(
217232
this.#requestTraceOptions,
218233
this.#client,
219234
listOptions
220-
);
221-
222-
for await (const setting of settings) {
223-
if (!isFeatureFlag(setting)) { // exclude feature flags
224-
loadedSettings.push(setting);
235+
).byPage();
236+
for await (const page of pageIterator) {
237+
pageEtags.push(page.etag ?? "");
238+
for (const setting of page.items) {
239+
if (!isFeatureFlag(setting)) { // exclude feature flags
240+
loadedSettings.push(setting);
241+
}
225242
}
226243
}
244+
selector.pageEtags = pageEtags;
227245
}
228246
return loadedSettings;
229247
}
@@ -232,10 +250,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
232250
* Update 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.
233251
*/
234252
async #updateWatchedKeyValuesEtag(existingSettings: ConfigurationSetting[]): Promise<void> {
235-
if (!this.#refreshEnabled) {
236-
return;
237-
}
238-
239253
for (const sentinel of this.#sentinels) {
240254
const matchedSetting = existingSettings.find(s => s.key === sentinel.key && s.label === sentinel.label);
241255
if (matchedSetting) {
@@ -256,7 +270,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
256270
async #loadSelectedAndWatchedKeyValues() {
257271
const keyValues: [key: string, value: unknown][] = [];
258272
const loadedSettings = await this.#loadSelectedKeyValues();
259-
await this.#updateWatchedKeyValuesEtag(loadedSettings);
273+
if (!this.#registerAll) {
274+
await this.#updateWatchedKeyValuesEtag(loadedSettings);
275+
}
260276

261277
// process key-values, watched settings have higher priority
262278
for (const setting of loadedSettings) {
@@ -413,19 +429,46 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
413429
return Promise.resolve(false);
414430
}
415431

416-
// try refresh if any of watched settings is changed.
417432
let needRefresh = false;
418-
for (const sentinel of this.#sentinels.values()) {
419-
const response = await this.#getConfigurationSetting(sentinel, {
420-
onlyIfChanged: true
421-
});
422-
423-
if (response?.statusCode === 200 // created or changed
424-
|| (response === undefined && sentinel.etag !== undefined) // deleted
425-
) {
426-
sentinel.etag = response?.etag;// update etag of the sentinel
427-
needRefresh = true;
428-
break;
433+
if (this.#registerAll) {
434+
// check whether there is any change based on page etags
435+
for (const selector of this.#featureFlagSelectors) {
436+
const listOptions: ListConfigurationSettingsOptions = {
437+
keyFilter: selector.keyFilter,
438+
labelFilter: selector.labelFilter,
439+
pageEtags: selector.pageEtags // when etags are provided, sdk will send conditional request
440+
};
441+
const pageIterator = listConfigurationSettingsWithTrace(
442+
this.#requestTraceOptions,
443+
this.#client,
444+
listOptions
445+
).byPage();
446+
447+
for await (const page of pageIterator) {
448+
if (page._response.status === 200) { // created or changed
449+
needRefresh = true;
450+
break;
451+
}
452+
}
453+
454+
if (needRefresh) {
455+
break; // short-circuit if result from any of the selectors is changed
456+
}
457+
}
458+
} else {
459+
// check whether there is any change based on watched settings etags
460+
for (const sentinel of this.#sentinels.values()) {
461+
const response = await this.#getConfigurationSetting(sentinel, {
462+
onlyIfChanged: true
463+
});
464+
465+
if (response?.statusCode === 200 // created or changed
466+
|| (response === undefined && sentinel.etag !== undefined) // deleted (404 exception is caught in #getConfigurationSetting)
467+
) {
468+
sentinel.etag = response?.etag;// update etag of the sentinel
469+
needRefresh = true;
470+
break;
471+
}
429472
}
430473
}
431474

@@ -779,15 +822,15 @@ function getValidSelectors(selectors: SettingSelector[]): SettingSelector[] {
779822
}
780823

781824
function getValidKeyValueSelectors(selectors?: SettingSelector[]): SettingSelector[] {
782-
if (!selectors || selectors.length === 0) {
825+
if (selectors === undefined || selectors.length === 0) {
783826
// Default selector: key: *, label: \0
784827
return [{ keyFilter: KeyFilter.Any, labelFilter: LabelFilter.Null }];
785828
}
786829
return getValidSelectors(selectors);
787830
}
788831

789832
function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSelector[] {
790-
if (!selectors || selectors.length === 0) {
833+
if (selectors === undefined || selectors.length === 0) {
791834
// selectors must be explicitly provided.
792835
throw new Error("Feature flag selectors must be provided.");
793836
} else {

src/RefreshOptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export interface RefreshOptions {
2424
* Any modifications to watched settings will refresh all settings loaded by the configuration provider when refresh() is called.
2525
*/
2626
watchedSettings?: WatchedSetting[];
27+
28+
/**
29+
* Specifies whether all configuration settings will be watched for changes on the server.
30+
*/
31+
registerAll?: boolean;
2732
}
2833

2934
export interface FeatureFlagRefreshOptions {

0 commit comments

Comments
 (0)