Skip to content

Commit 68e2306

Browse files
authored
Add option for detecting changes to flag overrides (#101)
* Add the ability to detect changes to the flag override map * Move createFlagOverridesFromMap to common-js to reduce redundancy * Bump version
1 parent 1ec2efe commit 68e2306

File tree

6 files changed

+146
-29
lines changed

6 files changed

+146
-29
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "configcat-common",
3-
"version": "9.1.0",
3+
"version": "9.2.0",
44
"description": "ConfigCat is a configuration as a service that lets you manage your features and configurations without actually deploying new code.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/FlagOverrides.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,31 @@ export interface IOverrideDataSource {
3131
}
3232

3333
export class MapOverrideDataSource implements IOverrideDataSource {
34-
private readonly map: { [name: string]: Setting } = {};
34+
private static getCurrentSettings(map: { [name: string]: NonNullable<SettingValue> }) {
35+
return Object.fromEntries(Object.entries(map)
36+
.map(([key, value]) => [key, Setting.fromValue(value)]));
37+
}
38+
39+
private readonly initialSettings: { [name: string]: Setting };
40+
private readonly map?: { [name: string]: NonNullable<SettingValue> };
41+
42+
private readonly ["constructor"]!: typeof MapOverrideDataSource;
3543

36-
constructor(map: { [name: string]: NonNullable<SettingValue> }) {
37-
this.map = Object.fromEntries(Object.entries(map).map(([key, value]) => {
38-
return [key, Setting.fromValue(value)];
39-
}));
44+
constructor(map: { [name: string]: NonNullable<SettingValue> }, watchChanges?: boolean) {
45+
this.initialSettings = this.constructor.getCurrentSettings(map);
46+
if (watchChanges) {
47+
this.map = map;
48+
}
4049
}
4150

4251
getOverrides(): Promise<{ [name: string]: Setting }> {
43-
return Promise.resolve(this.map);
52+
return Promise.resolve(this.getOverridesSync());
4453
}
4554

4655
getOverridesSync(): { [name: string]: Setting } {
47-
return this.map;
56+
return this.map
57+
? this.constructor.getCurrentSettings(this.map)
58+
: this.initialSettings;
4859
}
4960
}
5061

src/index.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import type { IAutoPollOptions, ILazyLoadingOptions, IManualPollOptions, Options
44
import { PollingMode } from "./ConfigCatClientOptions";
55
import type { IConfigCatLogger } from "./ConfigCatLogger";
66
import { ConfigCatConsoleLogger, LogLevel } from "./ConfigCatLogger";
7+
import { FlagOverrides, MapOverrideDataSource, OverrideBehaviour } from "./FlagOverrides";
78
import { setupPolyfills } from "./Polyfills";
9+
import type { SettingValue } from "./ProjectConfig";
810

911
setupPolyfills();
1012

@@ -37,6 +39,19 @@ export function createConsoleLogger(logLevel: LogLevel): IConfigCatLogger {
3739
return new ConfigCatConsoleLogger(logLevel);
3840
}
3941

42+
/**
43+
* Creates an instance of `FlagOverrides` that uses a map data source.
44+
* @param map The map that contains the overrides.
45+
* @param behaviour The override behaviour.
46+
* Specifies whether the local values should override the remote values
47+
* or local values should only be used when a remote value doesn't exist
48+
* or the local values should be used only.
49+
* @param watchChanges If set to `true`, the input map will be tracked for changes.
50+
*/
51+
export function createFlagOverridesFromMap(map: { [name: string]: NonNullable<SettingValue> }, behaviour: OverrideBehaviour, watchChanges?: boolean): FlagOverrides {
52+
return new FlagOverrides(new MapOverrideDataSource(map, watchChanges), behaviour);
53+
}
54+
4055
/* Public types for platform-specific SDKs */
4156

4257
// List types here which are required to implement the platform-specific SDKs but shouldn't be exposed to end users.
@@ -51,14 +66,10 @@ export type { OptionsBase } from "./ConfigCatClientOptions";
5166

5267
export type { IConfigCache } from "./ConfigCatCache";
5368

54-
export { ExternalConfigCache } from "./ConfigCatCache";
69+
export { InMemoryConfigCache, ExternalConfigCache } from "./ConfigCatCache";
5570

5671
export type { IEventProvider, IEventEmitter } from "./EventEmitter";
5772

58-
export type { IOverrideDataSource } from "./FlagOverrides";
59-
60-
export { FlagOverrides, MapOverrideDataSource } from "./FlagOverrides";
61-
6273
/* Public types for end users */
6374

6475
// List types here which are part of the public API of platform-specific SDKs, thus, should be exposed to end users.
@@ -101,7 +112,9 @@ export type { UserAttributeValue } from "./User";
101112

102113
export { User } from "./User";
103114

104-
export { OverrideBehaviour } from "./FlagOverrides";
115+
export type { FlagOverrides };
116+
117+
export { OverrideBehaviour };
105118

106119
export { ClientCacheState, RefreshResult } from "./ConfigServiceBase";
107120

test/ConfigV2EvaluationTests.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { assert } from "chai";
22
import "mocha";
3-
import { FlagOverrides, IManualPollOptions, MapOverrideDataSource, OverrideBehaviour, SettingValue, User, UserAttributeValue } from "../src";
3+
import { IManualPollOptions, OverrideBehaviour, SettingValue, User, UserAttributeValue } from "../src";
44
import { LogLevel, LoggerWrapper } from "../src/ConfigCatLogger";
5+
import { FlagOverrides, MapOverrideDataSource } from "../src/FlagOverrides";
56
import { RolloutEvaluator, evaluate, isAllowedValue } from "../src/RolloutEvaluator";
67
import { errorToString } from "../src/Utils";
78
import { CdnConfigLocation, LocalFileConfigLocation } from "./helpers/ConfigLocation";

test/OverrideTests.ts

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,117 @@ describe("Local Overrides", () => {
1515
sdkType: "common",
1616
sdkVersion: "1.0.0"
1717
};
18+
19+
const overrideMap = {
20+
enabledFeature: true,
21+
disabledFeature: false,
22+
intSetting: 5,
23+
doubleSetting: 3.14,
24+
stringSetting: "test"
25+
};
26+
1827
const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
1928
flagOverrides: {
20-
dataSource: new MapOverrideDataSource({
21-
enabledFeature: true,
22-
disabledFeature: false,
23-
intSetting: 5,
24-
doubleSetting: 3.14,
25-
stringSetting: "test"
26-
}),
29+
dataSource: new MapOverrideDataSource(overrideMap),
2730
behaviour: OverrideBehaviour.LocalOnly
2831
}
2932
}, null);
3033
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);
3134

32-
assert.equal(await client.getValueAsync("enabledFeature", false), true);
33-
assert.equal(await client.getValueAsync("disabledFeature", true), false);
34-
assert.equal(await client.getValueAsync("intSetting", 0), 5);
35-
assert.equal(await client.getValueAsync("doubleSetting", 0), 3.14);
36-
assert.equal(await client.getValueAsync("stringSetting", ""), "test");
35+
assert.equal(await client.getValueAsync("enabledFeature", null), true);
36+
assert.equal(await client.getValueAsync("disabledFeature", null), false);
37+
assert.equal(await client.getValueAsync("intSetting", null), 5);
38+
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
39+
assert.equal(await client.getValueAsync("stringSetting", null), "test");
40+
41+
overrideMap.disabledFeature = true;
42+
overrideMap.intSetting = -5;
43+
44+
assert.equal(await client.getValueAsync("enabledFeature", null), true);
45+
assert.equal(await client.getValueAsync("disabledFeature", null), false);
46+
assert.equal(await client.getValueAsync("intSetting", null), 5);
47+
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
48+
assert.equal(await client.getValueAsync("stringSetting", null), "test");
49+
});
50+
51+
it("Values from map - LocalOnly - watch changes - async", async () => {
52+
const configCatKernel: FakeConfigCatKernel = {
53+
configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"),
54+
sdkType: "common",
55+
sdkVersion: "1.0.0"
56+
};
57+
58+
const overrideMap = {
59+
enabledFeature: true,
60+
disabledFeature: false,
61+
intSetting: 5,
62+
doubleSetting: 3.14,
63+
stringSetting: "test"
64+
};
65+
66+
const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
67+
flagOverrides: {
68+
dataSource: new MapOverrideDataSource(overrideMap, true),
69+
behaviour: OverrideBehaviour.LocalOnly
70+
}
71+
}, null);
72+
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);
73+
74+
assert.equal(await client.getValueAsync("enabledFeature", null), true);
75+
assert.equal(await client.getValueAsync("disabledFeature", null), false);
76+
assert.equal(await client.getValueAsync("intSetting", null), 5);
77+
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
78+
assert.equal(await client.getValueAsync("stringSetting", null), "test");
79+
80+
overrideMap.disabledFeature = true;
81+
overrideMap.intSetting = -5;
82+
83+
assert.equal(await client.getValueAsync("enabledFeature", null), true);
84+
assert.equal(await client.getValueAsync("disabledFeature", null), true);
85+
assert.equal(await client.getValueAsync("intSetting", null), -5);
86+
assert.equal(await client.getValueAsync("doubleSetting", null), 3.14);
87+
assert.equal(await client.getValueAsync("stringSetting", null), "test");
88+
});
89+
90+
it("Values from map - LocalOnly - watch changes - sync", async () => {
91+
const configCatKernel: FakeConfigCatKernel = {
92+
configFetcher: new FakeConfigFetcherBase("{\"f\": { \"fakeKey\": { \"v\": false, \"p\": [], \"r\": [] } } }"),
93+
sdkType: "common",
94+
sdkVersion: "1.0.0"
95+
};
96+
97+
const overrideMap = {
98+
enabledFeature: true,
99+
disabledFeature: false,
100+
intSetting: 5,
101+
doubleSetting: 3.14,
102+
stringSetting: "test"
103+
};
104+
105+
const options: AutoPollOptions = new AutoPollOptions("localhost", "common", "1.0.0", {
106+
flagOverrides: {
107+
dataSource: new MapOverrideDataSource(overrideMap, true),
108+
behaviour: OverrideBehaviour.LocalOnly
109+
}
110+
}, null);
111+
const client: IConfigCatClient = new ConfigCatClient(options, configCatKernel);
112+
113+
let snapshot = client.snapshot();
114+
assert.equal(await snapshot.getValue("enabledFeature", null), true);
115+
assert.equal(await snapshot.getValue("disabledFeature", null), false);
116+
assert.equal(await snapshot.getValue("intSetting", null), 5);
117+
assert.equal(await snapshot.getValue("doubleSetting", null), 3.14);
118+
assert.equal(await snapshot.getValue("stringSetting", null), "test");
119+
120+
overrideMap.disabledFeature = true;
121+
overrideMap.intSetting = -5;
122+
123+
snapshot = client.snapshot();
124+
assert.equal(await snapshot.getValue("enabledFeature", null), true);
125+
assert.equal(await snapshot.getValue("disabledFeature", null), true);
126+
assert.equal(await snapshot.getValue("intSetting", null), -5);
127+
assert.equal(await snapshot.getValue("doubleSetting", null), 3.14);
128+
assert.equal(await snapshot.getValue("stringSetting", null), "test");
37129
});
38130

39131
it("Values from map - LocalOverRemote", async () => {

0 commit comments

Comments
 (0)