Skip to content

Commit d65f1da

Browse files
committed
etag-based refresh
1 parent 780d1b3 commit d65f1da

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

examples/refresh.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const settings = await load(connectionString, {
3131
console.log("Update the `message` in your App Configuration store using Azure portal or CLI.")
3232
console.log("First, update the `message` value, and then update the `sentinel` key value.")
3333

34+
// eslint-disable-next-line no-constant-condition
3435
while (true) {
3536
// Refreshing the configuration setting
3637
await settings.refresh();

src/AzureAppConfigurationImpl.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4-
import { AppConfigurationClient, ConfigurationSetting, ListConfigurationSettingsOptions } from "@azure/app-configuration";
4+
import { AppConfigurationClient, ConfigurationSetting, ConfigurationSettingId, ListConfigurationSettingsOptions } from "@azure/app-configuration";
55
import { AzureAppConfiguration } from "./AzureAppConfiguration";
66
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions";
77
import { IKeyValueAdapter } from "./IKeyValueAdapter";
@@ -27,6 +27,7 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
2727
private refreshIntervalInMs: number;
2828
private onRefreshListeners: LinkedList<() => any>;
2929
private lastUpdateTimestamp: number;
30+
private sentinels: ConfigurationSettingId[];
3031

3132
constructor(
3233
private client: AppConfigurationClient,
@@ -52,6 +53,15 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
5253
this.refreshIntervalInMs = refreshIntervalInMs;
5354
}
5455
}
56+
57+
this.sentinels = options.refreshOptions.watchedSettings?.map(setting => {
58+
const key = setting.key;
59+
const label = setting.label;
60+
if (key.includes("*") || label?.includes("*")) {
61+
throw new Error("Wildcard key or label filters are not supported for refresh.");
62+
}
63+
return { key, label };
64+
});
5565
}
5666

5767
// TODO: should add more adapters to process different type of values
@@ -81,6 +91,11 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
8191
const keyValuePair = await this.processKeyValues(setting);
8292
keyValues.push(keyValuePair);
8393
}
94+
// update etag of sentinels
95+
const matchedSentinel = this.sentinels.find(s => s.key === setting.key && (s.label ?? null) === setting.label); // Workaround: as undefined label represents the same with null.
96+
if (matchedSentinel) {
97+
matchedSentinel.etag = setting.etag;
98+
}
8499
}
85100
}
86101
for (const [k, v] of keyValues) {
@@ -91,7 +106,7 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
91106

92107
public async refresh(): Promise<void> {
93108
// if no refreshOptions set, return
94-
if (this.options?.refreshOptions === undefined || this.options.refreshOptions.watchedSettings.length === 0) {
109+
if (this.sentinels === undefined || this.sentinels.length === 0) {
95110
return Promise.resolve();
96111
}
97112
// if still within refresh interval, return
@@ -101,12 +116,15 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
101116
}
102117

103118
// try refresh if any of watched settings is changed.
104-
// TODO: watchedSettings as optional, etag based refresh if not specified.
105119
let needRefresh = false;
106-
for (const watchedSetting of this.options.refreshOptions.watchedSettings) {
107-
const response = await this.client.getConfigurationSetting(watchedSetting);
108-
const [key, value] = await this.processKeyValues(response);
109-
if (value !== this.get(key)) {
120+
for (const sentinel of this.sentinels) {
121+
const response = await this.client.getConfigurationSetting(sentinel, {
122+
onlyIfChanged: true
123+
// TODO: do we trace this request by adding custom headers?
124+
});
125+
if (response.statusCode !== 304) { // TODO: can be more robust, e.g. === 200?
126+
// sentinel changed.
127+
sentinel.etag = response.etag;// update etag of the sentinel
110128
needRefresh = true;
111129
break;
112130
}

0 commit comments

Comments
 (0)