Skip to content

Commit 311f61f

Browse files
committed
feat: support precomputed config for flags evaluation
1 parent 6b4725c commit 311f61f

File tree

1 file changed

+109
-3
lines changed

1 file changed

+109
-3
lines changed

src/client/eppo-client.ts

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +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';
17+
import { Configuration, PrecomputedConfig } from '../configuration';
1818
import ConfigurationRequestor from '../configuration-requestor';
1919
import { ConfigurationStore } from '../configuration-store';
2020
import { ISyncStore } from '../configuration-store/configuration-store';
@@ -40,7 +40,13 @@ import {
4040
DEFAULT_ENABLE_BANDITS,
4141
} from '../constants';
4242
import { EppoValue } from '../eppo_value';
43-
import { Evaluator, FlagEvaluation, noneResult, overrideResult } from '../evaluator';
43+
import {
44+
Evaluator,
45+
FlagEvaluation,
46+
FlagEvaluationWithoutDetails,
47+
noneResult,
48+
overrideResult,
49+
} from '../evaluator';
4450
import { BoundedEventQueue } from '../events/bounded-event-queue';
4551
import EventDispatcher from '../events/event-dispatcher';
4652
import NoOpEventDispatcher from '../events/no-op-event-dispatcher';
@@ -53,6 +59,7 @@ import FetchHttpClient from '../http-client';
5359
import {
5460
BanditModelData,
5561
FormatEnum,
62+
IObfuscatedPrecomputedBandit,
5663
IPrecomputedBandit,
5764
PrecomputedFlag,
5865
Variation,
@@ -80,6 +87,8 @@ import {
8087
import { ConfigurationPoller } from '../configuration-poller';
8188
import { ConfigurationFeed, ConfigurationSource } from '../configuration-feed';
8289
import { BroadcastChannel } from '../broadcast';
90+
import { getMD5Hash } from '../obfuscation';
91+
import { decodePrecomputedBandit, decodePrecomputedFlag } from '../decoding';
8392

8493
export interface IAssignmentDetails<T extends Variation['value'] | object> {
8594
variation: T;
@@ -1297,6 +1306,19 @@ export default class EppoClient {
12971306
);
12981307
}
12991308

1309+
const precomputed = config.getPrecomputedConfiguration();
1310+
if (precomputed && precomputed.subjectKey === subjectKey) {
1311+
// Short-circuit evaluation if we have a matching precomputed configuration.
1312+
return this.evaluatePrecomputedAssignment(
1313+
precomputed,
1314+
flagKey,
1315+
subjectKey,
1316+
subjectAttributes,
1317+
expectedVariationType,
1318+
flagEvaluationDetailsBuilder,
1319+
);
1320+
}
1321+
13001322
const flag = config.getFlag(flagKey);
13011323

13021324
if (flag === null) {
@@ -1311,7 +1333,7 @@ export default class EppoClient {
13111333
subjectKey,
13121334
subjectAttributes,
13131335
flagEvaluationDetails,
1314-
config.getFlagsConfiguration()?.response.environment.name ?? '',
1336+
config.getFlagsConfiguration()?.response.format ?? '',
13151337
);
13161338
}
13171339

@@ -1371,6 +1393,90 @@ export default class EppoClient {
13711393
return result;
13721394
}
13731395

1396+
private evaluatePrecomputedAssignment(
1397+
precomputed: PrecomputedConfig,
1398+
flagKey: string,
1399+
subjectKey: string,
1400+
subjectAttributes: Attributes,
1401+
expectedVariationType: VariationType | undefined,
1402+
flagEvaluationDetailsBuilder: FlagEvaluationDetailsBuilder,
1403+
): FlagEvaluation {
1404+
const obfuscatedKey = getMD5Hash(flagKey, precomputed.response.salt);
1405+
const obfuscatedFlag: PrecomputedFlag | undefined = precomputed.response.flags[obfuscatedKey];
1406+
const obfuscatedBandit: IObfuscatedPrecomputedBandit | undefined =
1407+
precomputed.response.bandits[obfuscatedKey];
1408+
const flag = obfuscatedFlag && decodePrecomputedFlag(obfuscatedFlag);
1409+
const bandit = obfuscatedBandit && decodePrecomputedBandit(obfuscatedBandit);
1410+
1411+
if (!flag) {
1412+
logger.warn(`${loggerPrefix} No assigned variation. Flag not found: ${flagKey}`);
1413+
// note: this is different from the Python SDK, which returns None instead
1414+
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult(
1415+
'FLAG_UNRECOGNIZED_OR_DISABLED',
1416+
`Unrecognized or disabled flag: ${flagKey}`,
1417+
);
1418+
return noneResult(
1419+
flagKey,
1420+
subjectKey,
1421+
subjectAttributes,
1422+
flagEvaluationDetails,
1423+
precomputed.response.format,
1424+
);
1425+
}
1426+
1427+
if (!checkTypeMatch(expectedVariationType, flag.variationType)) {
1428+
const errorMessage = `Variation value does not have the correct type. Found ${flag.variationType}, but expected ${expectedVariationType} for flag ${flagKey}`;
1429+
if (this.isGracefulFailureMode) {
1430+
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult(
1431+
'TYPE_MISMATCH',
1432+
errorMessage,
1433+
);
1434+
return noneResult(
1435+
flagKey,
1436+
subjectKey,
1437+
subjectAttributes,
1438+
flagEvaluationDetails,
1439+
precomputed.response.format,
1440+
);
1441+
}
1442+
throw new TypeError(errorMessage);
1443+
}
1444+
1445+
const result: FlagEvaluation = {
1446+
flagKey,
1447+
format: precomputed.response.format,
1448+
subjectKey: precomputed.subjectKey,
1449+
subjectAttributes: precomputed.subjectAttributes
1450+
? ensureNonContextualSubjectAttributes(precomputed.subjectAttributes)
1451+
: {},
1452+
variation: {
1453+
key: flag.variationKey ?? '',
1454+
value: flag.variationValue,
1455+
},
1456+
allocationKey: flag.allocationKey ?? '',
1457+
extraLogging: flag.extraLogging ?? {},
1458+
doLog: flag.doLog,
1459+
entityId: null,
1460+
flagEvaluationDetails: {
1461+
environmentName: precomputed.response.environment?.name ?? '',
1462+
flagEvaluationCode: 'MATCH',
1463+
flagEvaluationDescription: 'Matched precomputed flag',
1464+
variationKey: flag.variationKey ?? null,
1465+
variationValue: flag.variationValue,
1466+
banditKey: null,
1467+
banditAction: null,
1468+
configFetchedAt: precomputed.fetchedAt ?? '',
1469+
configPublishedAt: precomputed.response.createdAt,
1470+
matchedRule: null,
1471+
matchedAllocation: null,
1472+
unmatchedAllocations: [],
1473+
unevaluatedAllocations: [],
1474+
},
1475+
};
1476+
1477+
return result;
1478+
}
1479+
13741480
/**
13751481
* Enqueues an arbitrary event. Events must have a type and a payload.
13761482
*/

0 commit comments

Comments
 (0)