Skip to content

Commit 6df8767

Browse files
committed
refactor: add Configuration type
1 parent c2a07c1 commit 6df8767

File tree

7 files changed

+189
-96
lines changed

7 files changed

+189
-96
lines changed

src/client/eppo-client.ts

Lines changed: 39 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { AssignmentCache } from '../cache/abstract-assignment-cache';
1414
import { LRUInMemoryAssignmentCache } from '../cache/lru-in-memory-assignment-cache';
1515
import { NonExpiringInMemoryAssignmentCache } from '../cache/non-expiring-in-memory-cache-assignment';
1616
import { TLRUInMemoryAssignmentCache } from '../cache/tlru-in-memory-assignment-cache';
17+
import { Configuration } from '../configuration';
1718
import ConfigurationRequestor from '../configuration-requestor';
1819
import { ConfigurationStore } from '../configuration-store';
1920
import { IConfigurationStore, ISyncStore } from '../configuration-store/configuration-store';
@@ -30,7 +31,6 @@ import {
3031
DEFAULT_POLL_INTERVAL_MS,
3132
DEFAULT_REQUEST_TIMEOUT_MS,
3233
} from '../constants';
33-
import { decodeFlag } from '../decoding';
3434
import { EppoValue } from '../eppo_value';
3535
import { Evaluator, FlagEvaluation, noneResult, overrideResult } from '../evaluator';
3636
import { BoundedEventQueue } from '../events/bounded-event-queue';
@@ -42,19 +42,18 @@ import {
4242
} from '../flag-evaluation-details-builder';
4343
import { FlagEvaluationError } from '../flag-evaluation-error';
4444
import FetchHttpClient from '../http-client';
45-
import { Configuration, IConfiguration } from '../i-configuration';
4645
import {
4746
BanditModelData,
4847
BanditParameters,
4948
BanditVariation,
5049
Flag,
50+
FormatEnum,
5151
IPrecomputedBandit,
5252
ObfuscatedFlag,
5353
PrecomputedFlag,
5454
Variation,
5555
VariationType,
5656
} from '../interfaces';
57-
import { getMD5Hash } from '../obfuscation';
5857
import { OverridePayload, OverrideValidator } from '../override-validator';
5958
import initPoller, { IPoller } from '../poller';
6059
import {
@@ -106,6 +105,7 @@ export type EppoClientParameters = {
106105
banditModelConfigurationStore?: IConfigurationStore<BanditParameters>;
107106
overrideStore?: ISyncStore<Variation>;
108107
configurationRequestParameters?: FlagConfigurationRequestParameters;
108+
initialConfiguration?: Configuration;
109109
/**
110110
* Setting this value will have no side effects other than triggering a warning when the actual
111111
* configuration's obfuscated does not match the value set here.
@@ -135,7 +135,7 @@ export default class EppoClient {
135135
private configurationRequestor?: ConfigurationRequestor;
136136
private readonly overrideValidator = new OverrideValidator();
137137

138-
private readonly configurationStore = new ConfigurationStore(null);
138+
private readonly configurationStore;
139139
/** @deprecated use configurationStore instead. */
140140
private flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>;
141141
/** @deprecated use configurationStore instead. */
@@ -151,7 +151,10 @@ export default class EppoClient {
151151
banditModelConfigurationStore,
152152
overrideStore,
153153
configurationRequestParameters,
154+
initialConfiguration,
154155
}: EppoClientParameters) {
156+
this.configurationStore = new ConfigurationStore(initialConfiguration);
157+
155158
this.eventDispatcher = eventDispatcher;
156159
this.flagConfigurationStore = flagConfigurationStore;
157160
this.banditVariationConfigurationStore = banditVariationConfigurationStore;
@@ -167,10 +170,14 @@ export default class EppoClient {
167170
}
168171
}
169172

170-
private getConfiguration(): Configuration | null {
173+
public getConfiguration(): Configuration {
171174
return this.configurationStore.getConfiguration();
172175
}
173176

177+
public setConfiguration(configuration: Configuration) {
178+
this.configurationStore.setConfiguration(configuration);
179+
}
180+
174181
/**
175182
* Validates and parses x-eppo-overrides header sent by Eppo's Chrome extension
176183
*/
@@ -606,16 +613,13 @@ export default class EppoClient {
606613
defaultAction: string,
607614
): string {
608615
const config = this.getConfiguration();
609-
if (!config) {
610-
return defaultAction;
611-
}
612616
let result: string | null = null;
613617

614618
const flagBanditVariations = config.getFlagBanditVariations(flagKey);
615-
const banditKey = flagBanditVariations?.at(0)?.key;
619+
const banditKey = flagBanditVariations.at(0)?.key;
616620

617621
if (banditKey) {
618-
const banditParameters = config.getBandit(banditKey);
622+
const banditParameters = config.getBanditConfiguration()?.response.bandits[banditKey];
619623
if (banditParameters) {
620624
const contextualSubjectAttributes = ensureContextualSubjectAttributes(subjectAttributes);
621625
const actionsWithContextualAttributes = ensureActionsWithContextualAttributes(actions);
@@ -673,7 +677,6 @@ export default class EppoClient {
673677
// Note: the reason for non-bandit assignments include the subject being bucketed into a non-bandit variation or
674678
// a rollout having been done.
675679
const bandit = config.getFlagVariationBandit(flagKey, variation);
676-
677680
if (!bandit) {
678681
return { variation, action: null, evaluationDetails };
679682
}
@@ -901,29 +904,19 @@ export default class EppoClient {
901904
subjectAttributes: Attributes = {},
902905
): Record<FlagKey, PrecomputedFlag> {
903906
const config = this.getConfiguration();
904-
if (!config) {
905-
return {};
906-
}
907-
const configDetails = config.getFlagConfigDetails();
908907
const flagKeys = config.getFlagKeys();
909908
const flags: Record<FlagKey, PrecomputedFlag> = {};
910909

911910
// Evaluate all the enabled flags for the user
912911
flagKeys.forEach((flagKey) => {
913-
const flag = this.getNormalizedFlag(config, flagKey);
912+
const flag = config.getFlag(flagKey);
914913
if (!flag) {
915914
logger.debug(`${loggerPrefix} No assigned variation. Flag does not exist.`);
916915
return;
917916
}
918917

919918
// Evaluate the flag for this subject.
920-
const evaluation = this.evaluator.evaluateFlag(
921-
flag,
922-
configDetails,
923-
subjectKey,
924-
subjectAttributes,
925-
config.isObfuscated(),
926-
);
919+
const evaluation = this.evaluator.evaluateFlag(config, flag, subjectKey, subjectAttributes);
927920

928921
// allocationKey is set along with variation when there is a result. this check appeases typescript below
929922
if (!evaluation.variation || !evaluation.allocationKey) {
@@ -960,24 +953,10 @@ export default class EppoClient {
960953
banditActions: Record<FlagKey, BanditActions> = {},
961954
salt?: string,
962955
): string {
963-
const subjectContextualAttributes = ensureContextualSubjectAttributes(subjectAttributes);
964-
const subjectFlatAttributes = ensureNonContextualSubjectAttributes(subjectAttributes);
965-
966956
const config = this.getConfiguration();
967-
if (!config) {
968-
const precomputedConfig = PrecomputedConfiguration.obfuscated(
969-
subjectKey,
970-
{},
971-
{},
972-
salt ?? '',
973-
subjectContextualAttributes,
974-
undefined,
975-
);
976-
return JSON.stringify(ConfigurationWireV1.precomputed(precomputedConfig));
977-
}
978-
979-
const configDetails = config.getFlagConfigDetails();
980957

958+
const subjectContextualAttributes = ensureContextualSubjectAttributes(subjectAttributes);
959+
const subjectFlatAttributes = ensureNonContextualSubjectAttributes(subjectAttributes);
981960
const flags = this.getAllAssignments(subjectKey, subjectFlatAttributes);
982961

983962
const bandits = this.computeBanditsForFlags(
@@ -994,7 +973,7 @@ export default class EppoClient {
994973
bandits,
995974
salt ?? '', // no salt if not provided
996975
subjectContextualAttributes,
997-
configDetails.configEnvironment,
976+
config.getFlagsConfiguration()?.response.environment,
998977
);
999978

1000979
const configWire: IConfigurationWire = ConfigurationWireV1.precomputed(precomputedConfig);
@@ -1043,8 +1022,7 @@ export default class EppoClient {
10431022
);
10441023
}
10451024

1046-
const configDetails = config.getFlagConfigDetails();
1047-
const flag = this.getNormalizedFlag(config, flagKey);
1025+
const flag = config.getFlag(flagKey);
10481026

10491027
if (flag === null) {
10501028
logger.warn(`${loggerPrefix} No assigned variation. Flag not found: ${flagKey}`);
@@ -1058,7 +1036,7 @@ export default class EppoClient {
10581036
subjectKey,
10591037
subjectAttributes,
10601038
flagEvaluationDetails,
1061-
configDetails.configFormat,
1039+
config.getFlagsConfiguration()?.response.environment.name ?? '',
10621040
);
10631041
}
10641042

@@ -1074,7 +1052,7 @@ export default class EppoClient {
10741052
subjectKey,
10751053
subjectAttributes,
10761054
flagEvaluationDetails,
1077-
configDetails.configFormat,
1055+
config.getFlagsConfiguration()?.response.format ?? '',
10781056
);
10791057
}
10801058
throw new TypeError(errorMessage);
@@ -1092,23 +1070,20 @@ export default class EppoClient {
10921070
subjectKey,
10931071
subjectAttributes,
10941072
flagEvaluationDetails,
1095-
configDetails.configFormat,
1073+
config.getFlagsConfiguration()?.response.format ?? '',
10961074
);
10971075
}
10981076

1099-
const isObfuscated = config.isObfuscated();
11001077
const result = this.evaluator.evaluateFlag(
1078+
config,
11011079
flag,
1102-
configDetails,
11031080
subjectKey,
11041081
subjectAttributes,
1105-
isObfuscated,
11061082
expectedVariationType,
11071083
);
1108-
if (isObfuscated) {
1109-
// flag.key is obfuscated, replace with requested flag key
1110-
result.flagKey = flagKey;
1111-
}
1084+
1085+
// if flag.key is obfuscated, replace with requested flag key
1086+
result.flagKey = flagKey;
11121087

11131088
try {
11141089
if (result?.doLog) {
@@ -1134,36 +1109,22 @@ export default class EppoClient {
11341109
}
11351110

11361111
private newFlagEvaluationDetailsBuilder(
1137-
config: IConfiguration | null,
1112+
config: Configuration,
11381113
flagKey: string,
11391114
): FlagEvaluationDetailsBuilder {
1140-
if (!config) {
1141-
return new FlagEvaluationDetailsBuilder('', [], '', '');
1142-
}
1143-
1144-
const flag = this.getNormalizedFlag(config, flagKey);
1145-
const configDetails = config.getFlagConfigDetails();
1115+
const flag = config.getFlag(flagKey);
1116+
const flagsConfiguration = config.getFlagsConfiguration();
11461117
return new FlagEvaluationDetailsBuilder(
1147-
configDetails.configEnvironment.name,
1118+
flagsConfiguration?.response.environment.name ?? '',
11481119
flag?.allocations ?? [],
1149-
configDetails.configFetchedAt,
1150-
configDetails.configPublishedAt,
1120+
flagsConfiguration?.fetchedAt ?? '',
1121+
flagsConfiguration?.response.createdAt ?? '',
11511122
);
11521123
}
11531124

1154-
private getNormalizedFlag(config: IConfiguration, flagKey: string): Flag | null {
1155-
return config.isObfuscated()
1156-
? this.getObfuscatedFlag(config, flagKey)
1157-
: config.getFlag(flagKey);
1158-
}
1159-
1160-
private getObfuscatedFlag(config: IConfiguration, flagKey: string): Flag | null {
1161-
const flag: ObfuscatedFlag | null = config.getFlag(getMD5Hash(flagKey)) as ObfuscatedFlag;
1162-
return flag ? decodeFlag(flag) : null;
1163-
}
1164-
11651125
isInitialized() {
1166-
return this.getConfiguration()?.isInitialized() ?? false;
1126+
// We treat configuration as initialized if we have flags config.
1127+
return !!this.configurationStore.getConfiguration()?.getFlagsConfiguration();
11671128
}
11681129

11691130
/** @deprecated Use `setAssignmentLogger` */
@@ -1304,14 +1265,15 @@ export default class EppoClient {
13041265

13051266
private buildLoggerMetadata(): Record<string, unknown> {
13061267
return {
1307-
obfuscated: this.getConfiguration()?.isObfuscated(),
1268+
obfuscated:
1269+
this.getConfiguration()?.getFlagsConfiguration()?.response.format === FormatEnum.CLIENT,
13081270
sdkLanguage: 'javascript',
13091271
sdkLibVersion: LIB_VERSION,
13101272
};
13111273
}
13121274

13131275
private computeBanditsForFlags(
1314-
config: IConfiguration,
1276+
config: Configuration,
13151277
subjectKey: string,
13161278
subjectAttributes: ContextAttributes,
13171279
banditActions: Record<FlagKey, BanditActions>,
@@ -1341,7 +1303,7 @@ export default class EppoClient {
13411303
}
13421304

13431305
private getPrecomputedBandit(
1344-
config: IConfiguration,
1306+
config: Configuration,
13451307
flagKey: string,
13461308
variationValue: string,
13471309
subjectKey: string,

src/configuration-store/configuration-store.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Configuration } from '../i-configuration';
1+
import { Configuration } from '../configuration';
22
import { Environment } from '../interfaces';
33

44
/**
@@ -8,18 +8,18 @@ import { Environment } from '../interfaces';
88
* @internal `ConfigurationStore` shall only be used inside Eppo SDKs.
99
*/
1010
export class ConfigurationStore {
11-
private configuration: Configuration | null;
12-
private readonly listeners: Array<(configuration: Configuration | null) => void> = [];
11+
private configuration: Configuration;
12+
private readonly listeners: Array<(configuration: Configuration) => void> = [];
1313

14-
public constructor(configuration: Configuration | null) {
14+
public constructor(configuration: Configuration = Configuration.empty()) {
1515
this.configuration = configuration;
1616
}
1717

18-
public getConfiguration(): Configuration | null {
18+
public getConfiguration(): Configuration {
1919
return this.configuration;
2020
}
2121

22-
public setConfiguration(configuration: Configuration | null): void {
22+
public setConfiguration(configuration: Configuration): void {
2323
this.configuration = configuration;
2424
this.notifyListeners();
2525
}
@@ -30,9 +30,7 @@ export class ConfigurationStore {
3030
*
3131
* Returns a function to unsubscribe from future updates.
3232
*/
33-
public onConfigurationChange(
34-
listener: (configuration: Configuration | null) => void,
35-
): () => void {
33+
public onConfigurationChange(listener: (configuration: Configuration) => void): () => void {
3634
this.listeners.push(listener);
3735

3836
return () => {

0 commit comments

Comments
 (0)