Skip to content

Key vault secret refresh #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2720628
support startup retry and timeout
zhiyuanliang-ms Feb 17, 2025
c15cf1b
update
zhiyuanliang-ms Feb 17, 2025
15c7e54
update
zhiyuanliang-ms Feb 17, 2025
90c4159
update
zhiyuanliang-ms Feb 17, 2025
a0e6543
add testcase
zhiyuanliang-ms Feb 17, 2025
435ff08
clarify error type
zhiyuanliang-ms Feb 19, 2025
326bf46
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms Feb 19, 2025
6a8ceb7
update
zhiyuanliang-ms Feb 19, 2025
fc1aa5b
update
zhiyuanliang-ms Feb 19, 2025
7de8a0d
update
zhiyuanliang-ms Feb 20, 2025
5d23399
fix lint
zhiyuanliang-ms Feb 20, 2025
233af51
handle keyvault error
zhiyuanliang-ms Feb 20, 2025
a0e0792
update
zhiyuanliang-ms Feb 20, 2025
9e32db4
update
zhiyuanliang-ms Feb 21, 2025
3a33738
update
zhiyuanliang-ms Feb 23, 2025
c637682
update
zhiyuanliang-ms Feb 23, 2025
f079081
add secretRefreshIntervalInMs to KeyVaultOptions
zhiyuanliang-ms Feb 23, 2025
a9bcea4
update
zhiyuanliang-ms Feb 23, 2025
00e2e6b
update
zhiyuanliang-ms Feb 23, 2025
be55e5a
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Feb 23, 2025
1478e94
handle keyvault reference error
zhiyuanliang-ms Feb 23, 2025
d618684
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Feb 23, 2025
9b50135
update
zhiyuanliang-ms Feb 23, 2025
9657748
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Feb 23, 2025
190f7b1
wip
zhiyuanliang-ms Feb 23, 2025
39d9a3d
fix lint
zhiyuanliang-ms Feb 23, 2025
ddc4a50
add secret provider
zhiyuanliang-ms Feb 23, 2025
540ec3a
add testcases
zhiyuanliang-ms Feb 24, 2025
19de218
update
zhiyuanliang-ms Feb 24, 2025
3eab635
remove extra blank line
zhiyuanliang-ms Feb 24, 2025
546d66a
update
zhiyuanliang-ms Feb 24, 2025
f8b76ed
update
zhiyuanliang-ms Feb 26, 2025
7e63ad5
update
zhiyuanliang-ms Feb 26, 2025
fe9ad2f
add boot loop protection
zhiyuanliang-ms Feb 27, 2025
1a10c89
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Feb 27, 2025
48e1147
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms Feb 27, 2025
b19732d
update
zhiyuanliang-ms Mar 4, 2025
80108c9
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms Mar 13, 2025
009ccd5
update
zhiyuanliang-ms Mar 13, 2025
d61aba9
update testcase
zhiyuanliang-ms Mar 13, 2025
d81f8a9
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Mar 13, 2025
3d88c7a
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms Apr 23, 2025
d1ad647
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Apr 23, 2025
73a24d4
update
zhiyuanliang-ms Apr 23, 2025
3bf0ec7
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Apr 23, 2025
2c362ce
update testcase
zhiyuanliang-ms Apr 23, 2025
4201e6c
Merge branch 'zhiyuanliang/startup-timeout' of https://github.com/Azu…
zhiyuanliang-ms Apr 23, 2025
305fb0b
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 6, 2025
bb180c2
revert unintended change
zhiyuanliang-ms May 6, 2025
11b648c
update
zhiyuanliang-ms May 7, 2025
7a4b271
update testcase
zhiyuanliang-ms May 7, 2025
4389642
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 7, 2025
6d8c41f
always cached secret with version
zhiyuanliang-ms May 7, 2025
e11cbd0
update
zhiyuanliang-ms May 8, 2025
c6e4423
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 8, 2025
969c0dd
update error mesage
zhiyuanliang-ms May 8, 2025
16dff9f
prevent refresh secrets operation when key alue refreshed
zhiyuanliang-ms May 11, 2025
67e7e57
Merge branch 'zhiyuanliang/secret-refresh' of https://github.com/Azur…
zhiyuanliang-ms May 11, 2025
f70f491
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 13, 2025
2a8df74
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 14, 2025
bf3d971
refresh secrets in parallel
zhiyuanliang-ms May 14, 2025
affee1a
Merge branch 'main' of https://github.com/Azure/AppConfiguration-Java…
zhiyuanliang-ms May 14, 2025
a865bfd
update
zhiyuanliang-ms May 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 99 additions & 44 deletions src/AzureAppConfigurationImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IKeyValueAdapter } from "./IKeyValueAdapter.js";
import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter.js";
import { DEFAULT_STARTUP_TIMEOUT_IN_MS } from "./StartupOptions.js";
import { DEFAULT_REFRESH_INTERVAL_IN_MS, MIN_REFRESH_INTERVAL_IN_MS } from "./refresh/refreshOptions.js";
import { MIN_SECRET_REFRESH_INTERVAL_IN_MS } from "./keyvault/KeyVaultOptions.js";
import { Disposable } from "./common/disposable.js";
import {
FEATURE_FLAGS_KEY_NAME,
Expand Down Expand Up @@ -74,16 +75,22 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
/**
* Aka watched settings.
*/
#refreshEnabled: boolean = false;
#sentinels: ConfigurationSettingId[] = [];
#watchAll: boolean = false;
#kvRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
#kvRefreshTimer: RefreshTimer;

// Feature flags
#featureFlagEnabled: boolean = false;
#featureFlagRefreshEnabled: boolean = false;
#ffRefreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
#ffRefreshTimer: RefreshTimer;

// Key Vault references
#secretRefreshEnabled: boolean = false;
#secretReferences: ConfigurationSetting[] = []; // cached key vault references
#secretRefreshTimer: RefreshTimer;
#resolveSecretsInParallel: boolean = false;

/**
Expand Down Expand Up @@ -112,14 +119,15 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
this.#featureFlagTracing = new FeatureFlagTracingOptions();
}

if (options?.trimKeyPrefixes) {
if (options?.trimKeyPrefixes !== undefined) {
this.#sortedTrimKeyPrefixes = [...options.trimKeyPrefixes].sort((a, b) => b.localeCompare(a));
}

// if no selector is specified, always load key values using the default selector: key="*" and label="\0"
this.#kvSelectors = getValidKeyValueSelectors(options?.selectors);

if (options?.refreshOptions?.enabled) {
if (options?.refreshOptions?.enabled === true) {
this.#refreshEnabled = true;
const { refreshIntervalInMs, watchedSettings } = options.refreshOptions;
if (watchedSettings === undefined || watchedSettings.length === 0) {
this.#watchAll = true; // if no watched settings is specified, then watch all
Expand All @@ -139,53 +147,48 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
if (refreshIntervalInMs !== undefined) {
if (refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS) {
throw new RangeError(`The refresh interval cannot be less than ${MIN_REFRESH_INTERVAL_IN_MS} milliseconds.`);
} else {
this.#kvRefreshInterval = refreshIntervalInMs;
}
this.#kvRefreshInterval = refreshIntervalInMs;
}
this.#kvRefreshTimer = new RefreshTimer(this.#kvRefreshInterval);
}

// feature flag options
if (options?.featureFlagOptions?.enabled) {
if (options?.featureFlagOptions?.enabled === true) {
this.#featureFlagEnabled = true;
// validate feature flag selectors, only load feature flags when enabled
this.#ffSelectors = getValidFeatureFlagSelectors(options.featureFlagOptions.selectors);

if (options.featureFlagOptions.refresh?.enabled) {
if (options.featureFlagOptions.refresh?.enabled === true) {
this.#featureFlagRefreshEnabled = true;
const { refreshIntervalInMs } = options.featureFlagOptions.refresh;
// custom refresh interval
if (refreshIntervalInMs !== undefined) {
if (refreshIntervalInMs < MIN_REFRESH_INTERVAL_IN_MS) {
throw new RangeError(`The feature flag refresh interval cannot be less than ${MIN_REFRESH_INTERVAL_IN_MS} milliseconds.`);
} else {
this.#ffRefreshInterval = refreshIntervalInMs;
}
this.#ffRefreshInterval = refreshIntervalInMs;
}

this.#ffRefreshTimer = new RefreshTimer(this.#ffRefreshInterval);
}
}

if (options?.keyVaultOptions?.parallelSecretResolutionEnabled) {
this.#resolveSecretsInParallel = options.keyVaultOptions.parallelSecretResolutionEnabled;
if (options?.keyVaultOptions !== undefined) {
const { secretRefreshIntervalInMs } = options.keyVaultOptions;
if (secretRefreshIntervalInMs !== undefined) {
if (secretRefreshIntervalInMs < MIN_SECRET_REFRESH_INTERVAL_IN_MS) {
throw new RangeError(`The Key Vault secret refresh interval cannot be less than ${MIN_SECRET_REFRESH_INTERVAL_IN_MS} milliseconds.`);
}
this.#secretRefreshEnabled = true;
this.#secretRefreshTimer = new RefreshTimer(secretRefreshIntervalInMs);
}
this.#resolveSecretsInParallel = options.keyVaultOptions.parallelSecretResolutionEnabled ?? false;
}

this.#adapters.push(new AzureKeyVaultKeyValueAdapter(options?.keyVaultOptions));
this.#adapters.push(new AzureKeyVaultKeyValueAdapter(options?.keyVaultOptions, this.#secretRefreshTimer));
this.#adapters.push(new JsonKeyValueAdapter());
}

get #refreshEnabled(): boolean {
return !!this.#options?.refreshOptions?.enabled;
}

get #featureFlagEnabled(): boolean {
return !!this.#options?.featureFlagOptions?.enabled;
}

get #featureFlagRefreshEnabled(): boolean {
return this.#featureFlagEnabled && !!this.#options?.featureFlagOptions?.refresh?.enabled;
}

get #requestTraceOptions(): RequestTracingOptions {
return {
enabled: this.#requestTracingEnabled,
Expand Down Expand Up @@ -320,8 +323,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
* Refreshes the configuration.
*/
async refresh(): Promise<void> {
if (!this.#refreshEnabled && !this.#featureFlagRefreshEnabled) {
throw new InvalidOperationError("Refresh is not enabled for key-values or feature flags.");
if (!this.#refreshEnabled && !this.#featureFlagRefreshEnabled && !this.#secretRefreshEnabled) {
throw new InvalidOperationError("Refresh is not enabled for key-values, feature flags or Key Vault secrets.");
}

if (this.#refreshInProgress) {
Expand All @@ -339,8 +342,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
* Registers a callback function to be called when the configuration is refreshed.
*/
onRefresh(listener: () => any, thisArg?: any): Disposable {
if (!this.#refreshEnabled && !this.#featureFlagRefreshEnabled) {
throw new InvalidOperationError("Refresh is not enabled for key-values or feature flags.");
if (!this.#refreshEnabled && !this.#featureFlagRefreshEnabled && !this.#secretRefreshEnabled) {
throw new InvalidOperationError("Refresh is not enabled for key-values, feature flags or Key Vault secrets.");
}

const boundedListener = listener.bind(thisArg);
Expand Down Expand Up @@ -408,8 +411,20 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {

async #refreshTasks(): Promise<void> {
const refreshTasks: Promise<boolean>[] = [];
if (this.#refreshEnabled) {
refreshTasks.push(this.#refreshKeyValues());
if (this.#refreshEnabled || this.#secretRefreshEnabled) {
refreshTasks.push(
this.#refreshKeyValues()
.then(keyValueRefreshed => {
// Only refresh secrets if key values didn't change and secret refresh is enabled
// If key values are refreshed, all secret references will be refreshed as well.
if (!keyValueRefreshed && this.#secretRefreshEnabled) {
// Returns the refreshSecrets promise directly.
// in a Promise chain, this automatically flattens nested Promises without requiring await.
return this.#refreshSecrets();
}
return keyValueRefreshed;
})
);
}
if (this.#featureFlagRefreshEnabled) {
refreshTasks.push(this.#refreshFeatureFlags());
Expand Down Expand Up @@ -490,35 +505,32 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
* Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
*/
async #loadSelectedAndWatchedKeyValues() {
this.#secretReferences = []; // clear all cached key vault reference configuration settings
const keyValues: [key: string, value: unknown][] = [];
const loadedSettings: ConfigurationSetting[] = await this.#loadConfigurationSettings();
if (this.#refreshEnabled && !this.#watchAll) {
await this.#updateWatchedKeyValuesEtag(loadedSettings);
}

if (this.#requestTracingEnabled && this.#aiConfigurationTracing !== undefined) {
// Reset old AI configuration tracing in order to track the information present in the current response from server.
// reset old AI configuration tracing in order to track the information present in the current response from server
this.#aiConfigurationTracing.reset();
}

const secretResolutionPromises: Promise<void>[] = [];
for (const setting of loadedSettings) {
if (this.#resolveSecretsInParallel && isSecretReference(setting)) {
// secret references are resolved asynchronously to improve performance
const secretResolutionPromise = this.#processKeyValue(setting)
.then(([key, value]) => {
keyValues.push([key, value]);
});
secretResolutionPromises.push(secretResolutionPromise);
if (isSecretReference(setting)) {
this.#secretReferences.push(setting); // cache secret references for resolve/refresh secret separately
continue;
}
// adapt configuration settings to key-values
const [key, value] = await this.#processKeyValue(setting);
keyValues.push([key, value]);
}
if (secretResolutionPromises.length > 0) {
// wait for all secret resolution promises to be resolved
await Promise.all(secretResolutionPromises);

if (this.#secretReferences.length > 0) {
await this.#resolveSecretReferences(this.#secretReferences, (key, value) => {
keyValues.push([key, value]);
});
}

this.#clearLoadedKeyValues(); // clear existing key-values in case of configuration setting deletion
Expand Down Expand Up @@ -586,7 +598,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
*/
async #refreshKeyValues(): Promise<boolean> {
// if still within refresh interval/backoff, return
if (!this.#kvRefreshTimer.canRefresh()) {
if (this.#kvRefreshTimer === undefined || !this.#kvRefreshTimer.canRefresh()) {
return Promise.resolve(false);
}

Expand All @@ -610,6 +622,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
}

if (needRefresh) {
for (const adapter of this.#adapters) {
await adapter.onChangeDetected();
}
await this.#loadSelectedAndWatchedKeyValues();
}

Expand All @@ -623,7 +638,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
*/
async #refreshFeatureFlags(): Promise<boolean> {
// if still within refresh interval/backoff, return
if (!this.#ffRefreshTimer.canRefresh()) {
if (this.#ffRefreshInterval === undefined || !this.#ffRefreshTimer.canRefresh()) {
return Promise.resolve(false);
}

Expand All @@ -636,6 +651,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
return Promise.resolve(needRefresh);
}

async #refreshSecrets(): Promise<boolean> {
// if still within refresh interval/backoff, return
if (this.#secretRefreshTimer === undefined || !this.#secretRefreshTimer.canRefresh()) {
return Promise.resolve(false);
}

// if no cached key vault references, return
if (this.#secretReferences.length === 0) {
return Promise.resolve(false);
}

await this.#resolveSecretReferences(this.#secretReferences, (key, value) => {
this.#configMap.set(key, value);
});

this.#secretRefreshTimer.reset();
return Promise.resolve(true);
}

/**
* Checks whether the key-value collection has changed.
* @param selectors - The @see PagedSettingSelector of the kev-value collection.
Expand Down Expand Up @@ -738,6 +772,27 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
throw new Error("All fallback clients failed to get configuration settings.");
}

async #resolveSecretReferences(secretReferences: ConfigurationSetting[], resultHandler: (key: string, value: unknown) => void): Promise<void> {
if (this.#resolveSecretsInParallel) {
const secretResolutionPromises: Promise<void>[] = [];
for (const setting of secretReferences) {
const secretResolutionPromise = this.#processKeyValue(setting)
.then(([key, value]) => {
resultHandler(key, value);
});
secretResolutionPromises.push(secretResolutionPromise);
}

// Wait for all secret resolution promises to be resolved
await Promise.all(secretResolutionPromises);
} else {
for (const setting of secretReferences) {
const [key, value] = await this.#processKeyValue(setting);
resultHandler(key, value);
}
}
}

async #processKeyValue(setting: ConfigurationSetting<string>): Promise<[string, unknown]> {
this.#setAIConfigurationTracing(setting);

Expand Down
18 changes: 9 additions & 9 deletions src/ConfigurationClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ArgumentError } from "./common/error.js";

// Configuration client retry options
const CLIENT_MAX_RETRIES = 2;
const CLIENT_MAX_RETRY_DELAY = 60_000; // 1 minute in milliseconds
const CLIENT_MAX_RETRY_DELAY_IN_MS = 60_000;

const TCP_ORIGIN_KEY_NAME = "_origin._tcp";
const ALT_KEY_NAME = "_alt";
Expand All @@ -21,9 +21,9 @@ const ENDPOINT_KEY_NAME = "Endpoint";
const ID_KEY_NAME = "Id";
const SECRET_KEY_NAME = "Secret";
const TRUSTED_DOMAIN_LABELS = [".azconfig.", ".appconfig."];
const FALLBACK_CLIENT_EXPIRE_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
const MINIMAL_CLIENT_REFRESH_INTERVAL = 30_000; // 30 seconds in milliseconds
const DNS_RESOLVER_TIMEOUT = 3_000; // 3 seconds in milliseconds, in most cases, dns resolution should be within 200 milliseconds
const FALLBACK_CLIENT_EXPIRE_INTERVAL_IN_MS = 60 * 60 * 1000;
const MINIMAL_CLIENT_REFRESH_INTERVAL_IN_MS = 30_000;
const DNS_RESOLVER_TIMEOUT_IN_MS = 3_000;
const DNS_RESOLVER_TRIES = 2;
const MAX_ALTNATIVE_SRV_COUNT = 10;

Expand Down Expand Up @@ -120,11 +120,11 @@ export class ConfigurationClientManager {
const currentTime = Date.now();
// Filter static clients whose backoff time has ended
let availableClients = this.#staticClients.filter(client => client.backoffEndTime <= currentTime);
if (currentTime >= this.#lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL &&
if (currentTime >= this.#lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL_IN_MS &&
(!this.#dynamicClients ||
// All dynamic clients are in backoff means no client is available
this.#dynamicClients.every(client => currentTime < client.backoffEndTime) ||
currentTime >= this.#lastFallbackClientUpdateTime + FALLBACK_CLIENT_EXPIRE_INTERVAL)) {
currentTime >= this.#lastFallbackClientUpdateTime + FALLBACK_CLIENT_EXPIRE_INTERVAL_IN_MS)) {
await this.#discoverFallbackClients(this.endpoint.hostname);
return availableClients.concat(this.#dynamicClients);
}
Expand All @@ -142,7 +142,7 @@ export class ConfigurationClientManager {
async refreshClients() {
const currentTime = Date.now();
if (this.#isFailoverable &&
currentTime >= this.#lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL) {
currentTime >= this.#lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL_IN_MS) {
await this.#discoverFallbackClients(this.endpoint.hostname);
}
}
Expand Down Expand Up @@ -185,7 +185,7 @@ export class ConfigurationClientManager {

try {
// https://nodejs.org/api/dns.html#dnspromisesresolvesrvhostname
const resolver = new this.#dns.Resolver({timeout: DNS_RESOLVER_TIMEOUT, tries: DNS_RESOLVER_TRIES});
const resolver = new this.#dns.Resolver({timeout: DNS_RESOLVER_TIMEOUT_IN_MS, tries: DNS_RESOLVER_TRIES});
// On success, resolveSrv() returns an array of SrvRecord
// On failure, resolveSrv() throws an error with code 'ENOTFOUND'.
const originRecords = await resolver.resolveSrv(`${TCP_ORIGIN_KEY_NAME}.${host}`); // look up SRV records for the origin host
Expand Down Expand Up @@ -266,7 +266,7 @@ function getClientOptions(options?: AzureAppConfigurationOptions): AppConfigurat
// retry options
const defaultRetryOptions = {
maxRetries: CLIENT_MAX_RETRIES,
maxRetryDelayInMs: CLIENT_MAX_RETRY_DELAY,
maxRetryDelayInMs: CLIENT_MAX_RETRY_DELAY_IN_MS,
};
const retryOptions = Object.assign({}, defaultRetryOptions, options?.clientOptions?.retryOptions);

Expand Down
5 changes: 5 additions & 0 deletions src/IKeyValueAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ export interface IKeyValueAdapter {
* This method process the original configuration setting, and returns processed key and value in an array.
*/
processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]>;

/**
* This method is called when a change is detected in the configuration setting.
*/
onChangeDetected(setting?: ConfigurationSetting): Promise<void>;
}
4 changes: 4 additions & 0 deletions src/JsonKeyValueAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export class JsonKeyValueAdapter implements IKeyValueAdapter {
}
return [setting.key, parsedValue];
}

async onChangeDetected(): Promise<void> {
return;
}
}
2 changes: 1 addition & 1 deletion src/StartupOptions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

export const DEFAULT_STARTUP_TIMEOUT_IN_MS = 100 * 1000; // 100 seconds in milliseconds
export const DEFAULT_STARTUP_TIMEOUT_IN_MS = 100_000;

export interface StartupOptions {
/**
Expand Down
Loading