Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@eppo/js-client-sdk-common

Package Overview
Dependencies
Maintainers
8
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@eppo/js-client-sdk-common - npm Package Compare versions

Comparing version 3.6.0 to 4.0.0

dist/flag-evaluation-details-builder.d.ts

5

dist/assignment-logger.d.ts

@@ -0,1 +1,2 @@

import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';
export declare enum HoldoutVariationEnum {

@@ -38,2 +39,6 @@ STATUS_QUO = "status_quo",

metaData?: Record<string, unknown>;
/**
* The flag evaluation details
*/
evaluationDetails: IFlagEvaluationDetails;
}

@@ -40,0 +45,0 @@ /**

2

dist/bandit-logger.d.ts

@@ -0,1 +1,2 @@

import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';
import { Attributes } from './types';

@@ -16,2 +17,3 @@ export interface IBanditEvent {

metaData?: Record<string, unknown>;
evaluationDetails: IFlagEvaluationDetails;
}

@@ -18,0 +20,0 @@ export interface IBanditLogger {

189

dist/client/eppo-client.d.ts

@@ -6,11 +6,48 @@ import { IAssignmentLogger } from '../assignment-logger';

import { FlagEvaluation } from '../evaluator';
import { BanditVariation, BanditParameters, Flag, ObfuscatedFlag, VariationType } from '../interfaces';
import { Attributes, BanditActions, BanditSubjectAttributes, ValueType } from '../types';
/**
* Client for assigning experiment variations.
* @public
*/
export interface IEppoClient {
import { IFlagEvaluationDetails } from '../flag-evaluation-details-builder';
import { BanditParameters, BanditVariation, Flag, ObfuscatedFlag, Variation, VariationType } from '../interfaces';
import { AttributeType, Attributes, BanditActions, BanditSubjectAttributes, ValueType } from '../types';
export interface IAssignmentDetails<T extends Variation['value'] | object> {
variation: T;
action: string | null;
evaluationDetails: IFlagEvaluationDetails;
}
export declare type FlagConfigurationRequestParameters = {
apiKey: string;
sdkVersion: string;
sdkName: string;
baseUrl?: string;
requestTimeoutMs?: number;
numInitialRequestRetries?: number;
numPollRequestRetries?: number;
pollAfterSuccessfulInitialization?: boolean;
pollAfterFailedInitialization?: boolean;
throwOnFailedInitialization?: boolean;
skipInitialPoll?: boolean;
};
export default class EppoClient {
private flagConfigurationStore;
private banditVariationConfigurationStore?;
private banditModelConfigurationStore?;
private configurationRequestParameters?;
private isObfuscated;
private readonly queuedAssignmentEvents;
private assignmentLogger?;
private readonly queuedBanditEvents;
private banditLogger?;
private isGracefulFailureMode;
private assignmentCache?;
private requestPoller?;
private readonly evaluator;
private readonly banditEvaluator;
constructor(flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>, banditVariationConfigurationStore?: IConfigurationStore<BanditVariation[]> | undefined, banditModelConfigurationStore?: IConfigurationStore<BanditParameters> | undefined, configurationRequestParameters?: FlagConfigurationRequestParameters | undefined, isObfuscated?: boolean);
setConfigurationRequestParameters(configurationRequestParameters: FlagConfigurationRequestParameters): void;
setFlagConfigurationStore(flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>): void;
setBanditVariationConfigurationStore(banditVariationConfigurationStore: IConfigurationStore<BanditVariation[]>): void;
setBanditModelConfigurationStore(banditModelConfigurationStore: IConfigurationStore<BanditParameters>): void;
setIsObfuscated(isObfuscated: boolean): void;
fetchFlagConfigurations(): Promise<void>;
stopPolling(): void;
/**
* Maps a subject to a variation for a given experiment.
* Maps a subject to a string variation for a given experiment.
*

@@ -27,2 +64,15 @@ * @param flagKey feature flag identifier

/**
* Maps a subject to a string variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getStringAssignmentDetails(flagKey: string, subjectKey: string, subjectAttributes: Record<string, AttributeType>, defaultValue: string): IAssignmentDetails<string>;
/**
* @deprecated use getBooleanAssignment instead.

@@ -42,2 +92,15 @@ */

/**
* Maps a subject to a boolean variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getBooleanAssignmentDetails(flagKey: string, subjectKey: string, subjectAttributes: Record<string, AttributeType>, defaultValue: boolean): IAssignmentDetails<boolean>;
/**
* Maps a subject to an Integer variation for a given experiment.

@@ -49,7 +112,8 @@ *

* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value
* @returns an integer variation value if the subject is part of the experiment sample, otherwise the default value
*/
getIntegerAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: number): number;
/**
* Maps a subject to a Numeric variation for a given experiment.
* Maps a subject to an Integer variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*

@@ -60,2 +124,14 @@ * @param flagKey feature flag identifier

* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getIntegerAssignmentDetails(flagKey: string, subjectKey: string, subjectAttributes: Record<string, AttributeType>, defaultValue: number): IAssignmentDetails<number>;
/**
* Maps a subject to a numeric variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value

@@ -65,3 +141,4 @@ */

/**
* Maps a subject to a JSON variation for a given experiment.
* Maps a subject to a numeric variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*

@@ -72,84 +149,20 @@ * @param flagKey feature flag identifier

* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a JSON object variation value if the subject is part of the experiment sample, otherwise the default value
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getJSONAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: object): object;
getNumericAssignmentDetails(flagKey: string, subjectKey: string, subjectAttributes: Record<string, AttributeType>, defaultValue: number): IAssignmentDetails<number>;
/**
* Maps a subject to a string assignment for a given experiment.
* This variation may be a bandit-selected action.
* Maps a subject to a JSON variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional (can be empty) attributes associated with the subject, for example name and email.
* @param actions possible attributes and their optional (can be empty) attributes to be evaluated by a contextual,
* multi-armed bandit--if one is assigned to the subject.
* @param defaultValue default value to return if the subject is not part of the experiment sample,
* there are no bandit actions, or an error is countered evaluating the feature flag or bandit action */
getBanditAction(flagKey: string, subjectKey: string, subjectAttributes: BanditSubjectAttributes, actions: BanditActions, defaultValue: string): {
variation: string;
action: string | null;
};
/** @Deprecated Renamed to setAssignmentLogger for clarity */
setLogger(logger: IAssignmentLogger): void;
setAssignmentLogger(assignmentLogger: IAssignmentLogger): void;
setBanditLogger(banditLogger: IBanditLogger): void;
useLRUInMemoryAssignmentCache(maxSize: number): void;
useCustomAssignmentCache(cache: AssignmentCache): void;
setConfigurationRequestParameters(configurationRequestParameters: FlagConfigurationRequestParameters): void;
setFlagConfigurationStore(configurationStore: IConfigurationStore<Flag | ObfuscatedFlag>): void;
setBanditVariationConfigurationStore(banditVariationConfigurationStore: IConfigurationStore<BanditVariation[]>): void;
setBanditModelConfigurationStore(banditModelConfigurationStore: IConfigurationStore<BanditParameters>): void;
setIsObfuscated(isObfuscated: boolean): void;
fetchFlagConfigurations(): void;
stopPolling(): void;
setIsGracefulFailureMode(gracefulFailureMode: boolean): void;
getFlagKeys(): string[];
getFlagConfigurations(): Record<string, Flag>;
isInitialized(): boolean;
}
export declare type FlagConfigurationRequestParameters = {
apiKey: string;
sdkVersion: string;
sdkName: string;
baseUrl?: string;
requestTimeoutMs?: number;
numInitialRequestRetries?: number;
numPollRequestRetries?: number;
pollAfterSuccessfulInitialization?: boolean;
pollAfterFailedInitialization?: boolean;
throwOnFailedInitialization?: boolean;
skipInitialPoll?: boolean;
};
export default class EppoClient implements IEppoClient {
private flagConfigurationStore;
private banditVariationConfigurationStore?;
private banditModelConfigurationStore?;
private configurationRequestParameters?;
private isObfuscated;
private readonly queuedAssignmentEvents;
private assignmentLogger?;
private readonly queuedBanditEvents;
private banditLogger?;
private isGracefulFailureMode;
private assignmentCache?;
private requestPoller?;
private readonly evaluator;
private readonly banditEvaluator;
constructor(flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>, banditVariationConfigurationStore?: IConfigurationStore<BanditVariation[]> | undefined, banditModelConfigurationStore?: IConfigurationStore<BanditParameters> | undefined, configurationRequestParameters?: FlagConfigurationRequestParameters | undefined, isObfuscated?: boolean);
setConfigurationRequestParameters(configurationRequestParameters: FlagConfigurationRequestParameters): void;
setFlagConfigurationStore(flagConfigurationStore: IConfigurationStore<Flag | ObfuscatedFlag>): void;
setBanditVariationConfigurationStore(banditVariationConfigurationStore: IConfigurationStore<BanditVariation[]>): void;
setBanditModelConfigurationStore(banditModelConfigurationStore: IConfigurationStore<BanditParameters>): void;
setIsObfuscated(isObfuscated: boolean): void;
fetchFlagConfigurations(): Promise<void>;
stopPolling(): void;
getStringAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: string): string;
getBoolAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: boolean): boolean;
getBooleanAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: boolean): boolean;
getIntegerAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: number): number;
getNumericAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: number): number;
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a JSON object variation value if the subject is part of the experiment sample, otherwise the default value
*/
getJSONAssignment(flagKey: string, subjectKey: string, subjectAttributes: Attributes, defaultValue: object): object;
getBanditAction(flagKey: string, subjectKey: string, subjectAttributes: BanditSubjectAttributes, actions: BanditActions, defaultValue: string): {
variation: string;
action: string | null;
};
getJSONAssignmentDetails(flagKey: string, subjectKey: string, subjectAttributes: Record<string, AttributeType>, defaultValue: object): IAssignmentDetails<object>;
getBanditAction(flagKey: string, subjectKey: string, subjectAttributes: BanditSubjectAttributes, actions: BanditActions, defaultValue: string): Omit<IAssignmentDetails<string>, 'evaluationDetails'>;
getBanditActionDetails(flagKey: string, subjectKey: string, subjectAttributes: BanditSubjectAttributes, actions: BanditActions, defaultValue: string): IAssignmentDetails<string>;
private ensureNonContextualSubjectAttributes;

@@ -176,2 +189,4 @@ private ensureContextualSubjectAttributes;

getAssignmentDetail(flagKey: string, subjectKey: string, subjectAttributes?: Attributes, expectedVariationType?: VariationType): FlagEvaluation;
private flagEvaluationDetailsBuilder;
private getConfigDetails;
private getFlag;

@@ -178,0 +193,0 @@ private getObfuscatedFlag;

@@ -13,2 +13,3 @@ "use strict";

const evaluator_1 = require("../evaluator");
const flag_evaluation_details_builder_1 = require("../flag-evaluation-details-builder");
const http_client_1 = require("../http-client");

@@ -86,27 +87,175 @@ const interfaces_1 = require("../interfaces");

}
/**
* Maps a subject to a string variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns a variation value if the subject is part of the experiment sample, otherwise the default value
* @public
*/
getStringAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getStringAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a string variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getStringAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue) {
var _a;
return ((_a = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.String(defaultValue), interfaces_1.VariationType.STRING).stringValue) !== null && _a !== void 0 ? _a : defaultValue);
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.String(defaultValue), interfaces_1.VariationType.STRING);
return {
variation: (_a = eppoValue.stringValue) !== null && _a !== void 0 ? _a : defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* @deprecated use getBooleanAssignment instead.
*/
getBoolAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getBooleanAssignment(flagKey, subjectKey, subjectAttributes, defaultValue);
}
/**
* Maps a subject to a boolean variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a boolean variation value if the subject is part of the experiment sample, otherwise the default value
*/
getBooleanAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getBooleanAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a boolean variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getBooleanAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue) {
var _a;
return ((_a = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Bool(defaultValue), interfaces_1.VariationType.BOOLEAN).boolValue) !== null && _a !== void 0 ? _a : defaultValue);
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Bool(defaultValue), interfaces_1.VariationType.BOOLEAN);
return {
variation: (_a = eppoValue.boolValue) !== null && _a !== void 0 ? _a : defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to an Integer variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns an integer variation value if the subject is part of the experiment sample, otherwise the default value
*/
getIntegerAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getIntegerAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to an Integer variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getIntegerAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue) {
var _a;
return ((_a = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Numeric(defaultValue), interfaces_1.VariationType.INTEGER).numericValue) !== null && _a !== void 0 ? _a : defaultValue);
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Numeric(defaultValue), interfaces_1.VariationType.INTEGER);
return {
variation: (_a = eppoValue.numericValue) !== null && _a !== void 0 ? _a : defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to a numeric variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value
*/
getNumericAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getNumericAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a numeric variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
getNumericAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue) {
var _a;
return ((_a = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Numeric(defaultValue), interfaces_1.VariationType.NUMERIC).numericValue) !== null && _a !== void 0 ? _a : defaultValue);
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.Numeric(defaultValue), interfaces_1.VariationType.NUMERIC);
return {
variation: (_a = eppoValue.numericValue) !== null && _a !== void 0 ? _a : defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to a JSON variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a JSON object variation value if the subject is part of the experiment sample, otherwise the default value
*/
getJSONAssignment(flagKey, subjectKey, subjectAttributes, defaultValue) {
return this.getJSONAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
getJSONAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue) {
var _a;
return ((_a = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.JSON(defaultValue), interfaces_1.VariationType.JSON).objectValue) !== null && _a !== void 0 ? _a : defaultValue);
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(flagKey, subjectKey, subjectAttributes, eppo_value_1.EppoValue.JSON(defaultValue), interfaces_1.VariationType.JSON);
return {
variation: (_a = eppoValue.objectValue) !== null && _a !== void 0 ? _a : defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
getBanditAction(flagKey, subjectKey, subjectAttributes, actions, defaultValue) {
const { variation, action } = this.getBanditActionDetails(flagKey, subjectKey, subjectAttributes, actions, defaultValue);
return { variation, action };
}
getBanditActionDetails(flagKey, subjectKey, subjectAttributes, actions, defaultValue) {
var _a, _b, _c;
const flagEvaluationDetailsBuilder = this.flagEvaluationDetailsBuilder(flagKey);
const defaultResult = { variation: defaultValue, action: null };

@@ -120,3 +269,3 @@ let variation = defaultValue;

// we don't log a variation or bandit assignment
return defaultResult;
return Object.assign(Object.assign({}, defaultResult), { evaluationDetails: flagEvaluationDetailsBuilder.buildForNoneResult('NO_ACTIONS_SUPPLIED_FOR_BANDIT', 'No bandit actions passed for a flag known to have an active bandit') });
}

@@ -126,3 +275,4 @@ // Get the assigned variation for the flag with a possible bandit

const nonContextualSubjectAttributes = this.ensureNonContextualSubjectAttributes(subjectAttributes);
variation = this.getStringAssignment(flagKey, subjectKey, nonContextualSubjectAttributes, defaultValue);
const { variation: _variation, evaluationDetails } = this.getStringAssignmentDetails(flagKey, subjectKey, nonContextualSubjectAttributes, defaultValue);
variation = _variation;
// Check if the assigned variation is an active bandit

@@ -143,2 +293,4 @@ // Note: the reason for non-bandit assignments include the subject being bucketed into a non-bandit variation or

action = banditEvaluation.actionKey;
evaluationDetails.banditAction = action;
evaluationDetails.banditKey = banditKey;
const banditEvent = {

@@ -158,5 +310,7 @@ timestamp: new Date().toISOString(),

metaData: this.buildLoggerMetadata(),
evaluationDetails,
};
this.logBanditAction(banditEvent);
}
return { variation, action, evaluationDetails };
}

@@ -168,5 +322,4 @@ catch (err) {

}
return defaultResult;
return Object.assign(Object.assign({}, defaultResult), { evaluationDetails: flagEvaluationDetailsBuilder.buildForNoneResult('ASSIGNMENT_ERROR', `Error evaluating bandit action: ${err.message}`) });
}
return { variation, action };
}

@@ -257,8 +410,19 @@ ensureNonContextualSubjectAttributes(subjectAttributes) {

if (!result.variation) {
return defaultValue;
return {
eppoValue: defaultValue,
flagEvaluationDetails: result.flagEvaluationDetails,
};
}
return eppo_value_1.EppoValue.valueOf(result.variation.value, expectedVariationType);
return {
eppoValue: eppo_value_1.EppoValue.valueOf(result.variation.value, expectedVariationType),
flagEvaluationDetails: result.flagEvaluationDetails,
};
}
catch (error) {
return this.rethrowIfNotGraceful(error, defaultValue);
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue);
const flagEvaluationDetails = new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder('', [], '', '').buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`);
return {
eppoValue,
flagEvaluationDetails,
};
}

@@ -288,2 +452,4 @@ }

(0, validation_1.validateNotBlank)(flagKey, 'Invalid argument: flagKey cannot be blank');
const flagEvaluationDetailsBuilder = this.flagEvaluationDetailsBuilder(flagKey);
const configDetails = this.getConfigDetails();
const flag = this.getFlag(flagKey);

@@ -293,3 +459,4 @@ if (flag === null) {

// note: this is different from the Python SDK, which returns None instead
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes);
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult('FLAG_UNRECOGNIZED_OR_DISABLED', `Unrecognized or disabled flag: ${flagKey}`);
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}

@@ -302,5 +469,6 @@ if (!checkTypeMatch(expectedVariationType, flag.variationType)) {

// note: this is different from the Python SDK, which returns None instead
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes);
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult('FLAG_UNRECOGNIZED_OR_DISABLED', `Unrecognized or disabled flag: ${flagKey}`);
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}
const result = this.evaluator.evaluateFlag(flag, subjectKey, subjectAttributes, this.isObfuscated);
const result = this.evaluator.evaluateFlag(flag, configDetails, subjectKey, subjectAttributes, this.isObfuscated, expectedVariationType);
if (this.isObfuscated) {

@@ -311,3 +479,6 @@ // flag.key is obfuscated, replace with requested flag key

if ((result === null || result === void 0 ? void 0 : result.variation) && !checkValueTypeMatch(expectedVariationType, result.variation.value)) {
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes);
const { key: vKey, value: vValue } = result.variation;
const reason = `Expected variation type ${expectedVariationType} does not match for variation '${vKey}' with value ${vValue}`;
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult('TYPE_MISMATCH', reason);
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}

@@ -324,7 +495,20 @@ try {

}
flagEvaluationDetailsBuilder(flagKey) {
var _a;
const flag = this.getFlag(flagKey);
const configDetails = this.getConfigDetails();
return new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder(configDetails.configEnvironment.name, (_a = flag === null || flag === void 0 ? void 0 : flag.allocations) !== null && _a !== void 0 ? _a : [], configDetails.configFetchedAt, configDetails.configPublishedAt);
}
getConfigDetails() {
var _a, _b, _c;
return {
configFetchedAt: (_a = this.flagConfigurationStore.getConfigFetchedAt()) !== null && _a !== void 0 ? _a : '',
configPublishedAt: (_b = this.flagConfigurationStore.getConfigPublishedAt()) !== null && _b !== void 0 ? _b : '',
configEnvironment: (_c = this.flagConfigurationStore.getEnvironment()) !== null && _c !== void 0 ? _c : { name: '' },
};
}
getFlag(flagKey) {
if (this.isObfuscated) {
return this.getObfuscatedFlag(flagKey);
}
return this.flagConfigurationStore.get(flagKey);
return this.isObfuscated
? this.getObfuscatedFlag(flagKey)
: this.flagConfigurationStore.get(flagKey);
}

@@ -405,3 +589,3 @@ getObfuscatedFlag(flagKey) {

const { flagKey, subjectKey, allocationKey, subjectAttributes, variation } = result;
const event = Object.assign(Object.assign({}, ((_a = result.extraLogging) !== null && _a !== void 0 ? _a : {})), { allocation: allocationKey !== null && allocationKey !== void 0 ? allocationKey : null, experiment: allocationKey ? `${flagKey}-${allocationKey}` : null, featureFlag: flagKey, variation: (_b = variation === null || variation === void 0 ? void 0 : variation.key) !== null && _b !== void 0 ? _b : null, subject: subjectKey, timestamp: new Date().toISOString(), subjectAttributes, metaData: this.buildLoggerMetadata() });
const event = Object.assign(Object.assign({}, ((_a = result.extraLogging) !== null && _a !== void 0 ? _a : {})), { allocation: allocationKey !== null && allocationKey !== void 0 ? allocationKey : null, experiment: allocationKey ? `${flagKey}-${allocationKey}` : null, featureFlag: flagKey, variation: (_b = variation === null || variation === void 0 ? void 0 : variation.key) !== null && _b !== void 0 ? _b : null, subject: subjectKey, timestamp: new Date().toISOString(), subjectAttributes, metaData: this.buildLoggerMetadata(), evaluationDetails: result.flagEvaluationDetails });
if (variation && allocationKey) {

@@ -408,0 +592,0 @@ const hasLoggedAssignment = (_c = this.assignmentCache) === null || _c === void 0 ? void 0 : _c.has({

@@ -11,4 +11,5 @@ import { IConfigurationStore } from './configuration-store/configuration-store';

fetchAndStoreConfigurations(): Promise<void>;
private hydrateConfigurationStore;
private indexBanditVariationsByFlagKey;
}
//# sourceMappingURL=configuration-requestor.d.ts.map

@@ -12,3 +12,3 @@ "use strict";

async fetchAndStoreConfigurations() {
var _a, _b;
var _a;
const configResponse = await this.httpClient.getUniversalFlagConfiguration();

@@ -18,3 +18,7 @@ if (!(configResponse === null || configResponse === void 0 ? void 0 : configResponse.flags)) {

}
await this.flagConfigurationStore.setEntries(configResponse.flags);
await this.hydrateConfigurationStore(this.flagConfigurationStore, {
entries: configResponse.flags,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
const flagsHaveBandits = Object.keys((_a = configResponse.bandits) !== null && _a !== void 0 ? _a : {}).length > 0;

@@ -25,3 +29,7 @@ const banditStoresProvided = Boolean(this.banditVariationConfigurationStore && this.banditModelConfigurationStore);

const banditVariations = this.indexBanditVariationsByFlagKey(configResponse.bandits);
(_b = this.banditVariationConfigurationStore) === null || _b === void 0 ? void 0 : _b.setEntries(banditVariations);
await this.hydrateConfigurationStore(this.banditVariationConfigurationStore, {
entries: banditVariations,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
// TODO: different polling intervals for bandit parameters

@@ -33,6 +41,20 @@ const banditResponse = await this.httpClient.getBanditParameters();

}
await this.banditModelConfigurationStore.setEntries(banditResponse.bandits);
await this.hydrateConfigurationStore(this.banditModelConfigurationStore, {
entries: banditResponse.bandits,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
}
}
}
async hydrateConfigurationStore(configurationStore, response) {
if (configurationStore) {
const didUpdate = await configurationStore.setEntries(response.entries);
if (didUpdate) {
configurationStore.setEnvironment(response.environment);
configurationStore.setConfigFetchedAt(new Date().toISOString());
configurationStore.setConfigPublishedAt(response.createdAt);
}
}
}
indexBanditVariationsByFlagKey(banditVariationsByBanditKey) {

@@ -39,0 +61,0 @@ const banditVariationsByFlagKey = {};

@@ -0,1 +1,2 @@

import { Environment } from '../interfaces';
/**

@@ -30,3 +31,9 @@ * ConfigurationStore interface

isExpired(): Promise<boolean>;
setEntries(entries: Record<string, T>): Promise<void>;
setEntries(entries: Record<string, T>): Promise<boolean>;
setEnvironment(environment: Environment): void;
getEnvironment(): Environment | null;
getConfigFetchedAt(): string | null;
setConfigFetchedAt(configFetchedAt: string): void;
getConfigPublishedAt(): string | null;
setConfigPublishedAt(configPublishedAt: string): void;
}

@@ -33,0 +40,0 @@ export interface ISyncStore<T> {

@@ -0,1 +1,2 @@

import { Environment } from '../interfaces';
import { IAsyncStore, IConfigurationStore, ISyncStore } from './configuration-store';

@@ -6,2 +7,5 @@ export declare class HybridConfigurationStore<T> implements IConfigurationStore<T> {

constructor(servingStore: ISyncStore<T>, persistentStore: IAsyncStore<T> | null);
private environment;
private configFetchedAt;
private configPublishedAt;
/**

@@ -16,4 +20,10 @@ * Initialize the configuration store by loading the entries from the persistent store into the serving store.

getKeys(): string[];
setEntries(entries: Record<string, T>): Promise<void>;
setEntries(entries: Record<string, T>): Promise<boolean>;
setEnvironment(environment: Environment): void;
getEnvironment(): Environment | null;
getConfigFetchedAt(): string | null;
setConfigFetchedAt(configFetchedAt: string): void;
getConfigPublishedAt(): string | null;
setConfigPublishedAt(configPublishedAt: string): void;
}
//# sourceMappingURL=hybrid.store.d.ts.map

@@ -9,2 +9,5 @@ "use strict";

this.persistentStore = persistentStore;
this.environment = null;
this.configFetchedAt = null;
this.configPublishedAt = null;
}

@@ -58,5 +61,24 @@ /**

this.servingStore.setEntries(entries);
return true;
}
setEnvironment(environment) {
this.environment = environment;
}
getEnvironment() {
return this.environment;
}
getConfigFetchedAt() {
return this.configFetchedAt;
}
setConfigFetchedAt(configFetchedAt) {
this.configFetchedAt = configFetchedAt;
}
getConfigPublishedAt() {
return this.configPublishedAt;
}
setConfigPublishedAt(configPublishedAt) {
this.configPublishedAt = configPublishedAt;
}
}
exports.HybridConfigurationStore = HybridConfigurationStore;
//# sourceMappingURL=hybrid.store.js.map

@@ -0,1 +1,2 @@

import { Environment } from '../interfaces';
import { IConfigurationStore, ISyncStore } from './configuration-store';

@@ -14,2 +15,5 @@ export declare class MemoryStore<T> implements ISyncStore<T> {

private initialized;
private configFetchedAt;
private configPublishedAt;
private environment;
init(): Promise<void>;

@@ -21,4 +25,10 @@ get(key: string): T | null;

isInitialized(): boolean;
setEntries(entries: Record<string, T>): Promise<void>;
setEntries(entries: Record<string, T>): Promise<boolean>;
getEnvironment(): Environment | null;
setEnvironment(environment: Environment): void;
getConfigFetchedAt(): string | null;
setConfigFetchedAt(configFetchedAt: string): void;
getConfigPublishedAt(): string | null;
setConfigPublishedAt(configPublishedAt: string): void;
}
//# sourceMappingURL=memory.store.d.ts.map

@@ -32,2 +32,5 @@ "use strict";

this.initialized = false;
this.configFetchedAt = null;
this.configPublishedAt = null;
this.environment = null;
}

@@ -56,5 +59,24 @@ init() {

this.initialized = true;
return true;
}
getEnvironment() {
return this.environment;
}
setEnvironment(environment) {
this.environment = environment;
}
getConfigFetchedAt() {
return this.configFetchedAt;
}
setConfigFetchedAt(configFetchedAt) {
this.configFetchedAt = configFetchedAt;
}
getConfigPublishedAt() {
return this.configPublishedAt;
}
setConfigPublishedAt(configPublishedAt) {
this.configPublishedAt = configPublishedAt;
}
}
exports.MemoryOnlyConfigurationStore = MemoryOnlyConfigurationStore;
//# sourceMappingURL=memory.store.js.map

@@ -1,2 +0,3 @@

import { Flag, Shard, Range, Variation } from './interfaces';
import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';
import { Flag, Shard, Range, Variation, VariationType, ConfigDetails } from './interfaces';
import { Rule } from './rules';

@@ -13,2 +14,3 @@ import { Sharder } from './sharders';

doLog: boolean;
flagEvaluationDetails: IFlagEvaluationDetails;
}

@@ -18,9 +20,13 @@ export declare class Evaluator {

constructor(sharder?: Sharder);
evaluateFlag(flag: Flag, subjectKey: string, subjectAttributes: Attributes, obfuscated: boolean): FlagEvaluation;
evaluateFlag(flag: Flag, configDetails: ConfigDetails, subjectKey: string, subjectAttributes: Attributes, obfuscated: boolean, expectedVariationType?: VariationType): FlagEvaluation;
matchesShard(shard: Shard, subjectKey: string, totalShards: number): boolean;
private getMatchedEvaluationDetailsMessage;
}
export declare function isInShardRange(shard: number, range: Range): boolean;
export declare function hashKey(salt: string, subjectKey: string): string;
export declare function noneResult(flagKey: string, subjectKey: string, subjectAttributes: Attributes): FlagEvaluation;
export declare function matchesRules(rules: Rule[], subjectAttributes: Attributes, obfuscated: boolean): boolean;
export declare function noneResult(flagKey: string, subjectKey: string, subjectAttributes: Attributes, flagEvaluationDetails: IFlagEvaluationDetails): FlagEvaluation;
export declare function matchesRules(rules: Rule[], subjectAttributes: Attributes, obfuscated: boolean): {
matched: boolean;
matchedRule: Rule | null;
};
//# sourceMappingURL=evaluator.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchesRules = exports.noneResult = exports.hashKey = exports.isInShardRange = exports.Evaluator = void 0;
const flag_evaluation_details_builder_1 = require("./flag-evaluation-details-builder");
const rules_1 = require("./rules");

@@ -8,18 +9,51 @@ const sharders_1 = require("./sharders");

constructor(sharder) {
this.getMatchedEvaluationDetailsMessage = (allocation, split, subjectKey) => {
var _a;
const hasDefinedRules = !!((_a = allocation.rules) === null || _a === void 0 ? void 0 : _a.length);
const isExperiment = allocation.splits.length > 1;
const isPartialRollout = split.shards.length > 1;
const isExperimentOrPartialRollout = isExperiment || isPartialRollout;
if (hasDefinedRules && isExperimentOrPartialRollout) {
return `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`;
}
if (hasDefinedRules && !isExperimentOrPartialRollout) {
return `Supplied attributes match rules defined in allocation "${allocation.key}".`;
}
return `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`;
};
this.sharder = sharder !== null && sharder !== void 0 ? sharder : new sharders_1.MD5Sharder();
}
evaluateFlag(flag, subjectKey, subjectAttributes, obfuscated) {
evaluateFlag(flag, configDetails, subjectKey, subjectAttributes, obfuscated, expectedVariationType) {
var _a, _b;
const flagEvaluationDetailsBuilder = new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder(configDetails.configEnvironment.name, flag.allocations, configDetails.configFetchedAt, configDetails.configPublishedAt);
if (!flag.enabled) {
return noneResult(flag.key, subjectKey, subjectAttributes);
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder.buildForNoneResult('FLAG_UNRECOGNIZED_OR_DISABLED', `Unrecognized or disabled flag: ${flag.key}`));
}
const now = new Date();
for (const allocation of flag.allocations) {
if (allocation.startAt && now < new Date(allocation.startAt))
const unmatchedAllocations = [];
for (let i = 0; i < flag.allocations.length; i++) {
const allocation = flag.allocations[i];
const addUnmatchedAllocation = (code) => {
unmatchedAllocations.push({
key: allocation.key,
allocationEvaluationCode: code,
orderPosition: i + 1,
});
};
if (allocation.startAt && now < new Date(allocation.startAt)) {
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.BEFORE_START_TIME);
continue;
if (allocation.endAt && now > new Date(allocation.endAt))
}
if (allocation.endAt && now > new Date(allocation.endAt)) {
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.AFTER_END_TIME);
continue;
if (matchesRules((_a = allocation === null || allocation === void 0 ? void 0 : allocation.rules) !== null && _a !== void 0 ? _a : [], Object.assign({ id: subjectKey }, subjectAttributes), obfuscated)) {
}
const { matched, matchedRule } = matchesRules((_a = allocation === null || allocation === void 0 ? void 0 : allocation.rules) !== null && _a !== void 0 ? _a : [], Object.assign({ id: subjectKey }, subjectAttributes), obfuscated);
if (matched) {
for (const split of allocation.splits) {
if (split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))) {
const variation = flag.variations[split.variationKey];
const flagEvaluationDetails = flagEvaluationDetailsBuilder
.setMatch(i, variation, allocation, matchedRule, unmatchedAllocations, expectedVariationType)
.build('MATCH', this.getMatchedEvaluationDetailsMessage(allocation, split, subjectKey));
return {

@@ -30,11 +64,19 @@ flagKey: flag.key,

allocationKey: allocation.key,
variation: flag.variations[split.variationKey],
variation,
extraLogging: (_b = split.extraLogging) !== null && _b !== void 0 ? _b : {},
doLog: allocation.doLog,
flagEvaluationDetails,
};
}
}
// matched, but does not fall within split range
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS);
}
else {
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.FAILING_RULE);
}
}
return noneResult(flag.key, subjectKey, subjectAttributes);
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder
.setNoMatchFound(unmatchedAllocations)
.build('DEFAULT_ALLOCATION_NULL', 'No allocations matched. Falling back to "Default Allocation", serving NULL'));
}

@@ -55,3 +97,3 @@ matchesShard(shard, subjectKey, totalShards) {

exports.hashKey = hashKey;
function noneResult(flagKey, subjectKey, subjectAttributes) {
function noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails) {
return {

@@ -65,2 +107,3 @@ flagKey,

doLog: false,
flagEvaluationDetails,
};

@@ -70,5 +113,27 @@ }

function matchesRules(rules, subjectAttributes, obfuscated) {
return !rules.length || rules.some((rule) => (0, rules_1.matchesRule)(rule, subjectAttributes, obfuscated));
if (!rules.length) {
return {
matched: true,
matchedRule: null,
};
}
let matchedRule = null;
const hasMatch = rules.some((rule) => {
const matched = (0, rules_1.matchesRule)(rule, subjectAttributes, obfuscated);
if (matched) {
matchedRule = rule;
}
return matched;
});
return hasMatch
? {
matched: true,
matchedRule,
}
: {
matched: false,
matchedRule: null,
};
}
exports.matchesRules = matchesRules;
//# sourceMappingURL=evaluator.js.map
import ApiEndpoints from './api-endpoints';
import { BanditVariation, BanditParameters, Flag } from './interfaces';
import { BanditParameters, BanditVariation, Environment, Flag } from './interfaces';
export interface IQueryParams {

@@ -15,2 +15,4 @@ apiKey: string;

export interface IUniversalFlagConfigResponse {
createdAt: string;
environment: Environment;
flags: Record<string, Flag>;

@@ -17,0 +19,0 @@ bandits: Record<string, BanditVariation[]>;

@@ -7,3 +7,3 @@ import ApiEndpoints from './api-endpoints';

import { AbstractAssignmentCache, AssignmentCache, NonExpiringInMemoryAssignmentCache, LRUInMemoryAssignmentCache, AsyncMap, AssignmentCacheKey, AssignmentCacheValue, AssignmentCacheEntry, assignmentCacheKeyToString, assignmentCacheValueToString } from './cache/abstract-assignment-cache';
import EppoClient, { FlagConfigurationRequestParameters, IEppoClient } from './client/eppo-client';
import EppoClient, { FlagConfigurationRequestParameters, IAssignmentDetails } from './client/eppo-client';
import FlagConfigRequestor from './configuration-requestor';

@@ -18,3 +18,3 @@ import { IConfigurationStore, IAsyncStore, ISyncStore } from './configuration-store/configuration-store';

import * as validation from './validation';
export { applicationLogger, AbstractAssignmentCache, IAssignmentHooks, IAssignmentLogger, IAssignmentEvent, IBanditLogger, IBanditEvent, EppoClient, IEppoClient, constants, ApiEndpoints, FlagConfigRequestor, HttpClient, validation, IConfigurationStore, IAsyncStore, ISyncStore, MemoryStore, HybridConfigurationStore, MemoryOnlyConfigurationStore, AssignmentCacheKey, AssignmentCacheValue, AssignmentCacheEntry, AssignmentCache, AsyncMap, NonExpiringInMemoryAssignmentCache, LRUInMemoryAssignmentCache, assignmentCacheKeyToString, assignmentCacheValueToString, FlagConfigurationRequestParameters, Flag, ObfuscatedFlag, VariationType, AttributeType, Attributes, ContextAttributes, BanditSubjectAttributes, BanditActions, };
export { applicationLogger, AbstractAssignmentCache, IAssignmentDetails, IAssignmentHooks, IAssignmentLogger, IAssignmentEvent, IBanditLogger, IBanditEvent, EppoClient, constants, ApiEndpoints, FlagConfigRequestor, HttpClient, validation, IConfigurationStore, IAsyncStore, ISyncStore, MemoryStore, HybridConfigurationStore, MemoryOnlyConfigurationStore, AssignmentCacheKey, AssignmentCacheValue, AssignmentCacheEntry, AssignmentCache, AsyncMap, NonExpiringInMemoryAssignmentCache, LRUInMemoryAssignmentCache, assignmentCacheKeyToString, assignmentCacheValueToString, FlagConfigurationRequestParameters, Flag, ObfuscatedFlag, VariationType, AttributeType, Attributes, ContextAttributes, BanditSubjectAttributes, BanditActions, };
//# sourceMappingURL=index.d.ts.map

@@ -34,2 +34,10 @@ import { Rule } from './rules';

}
export interface Environment {
name: string;
}
export interface ConfigDetails {
configFetchedAt: string;
configPublishedAt: string;
configEnvironment: Environment;
}
export interface Flag {

@@ -45,2 +53,3 @@ key: string;

key: string;
environment: Environment;
enabled: boolean;

@@ -47,0 +56,0 @@ variationType: VariationType;

{
"name": "@eppo/js-client-sdk-common",
"version": "3.6.0",
"version": "4.0.0",
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -0,1 +1,3 @@

import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';
export enum HoldoutVariationEnum {

@@ -49,2 +51,7 @@ STATUS_QUO = 'status_quo',

metaData?: Record<string, unknown>;
/**
* The flag evaluation details
*/
evaluationDetails: IFlagEvaluationDetails;
}

@@ -51,0 +58,0 @@

@@ -0,1 +1,2 @@

import { IFlagEvaluationDetails } from './flag-evaluation-details-builder';
import { Attributes } from './types';

@@ -17,2 +18,3 @@

metaData?: Record<string, unknown>;
evaluationDetails: IFlagEvaluationDetails;
}

@@ -19,0 +21,0 @@

@@ -16,2 +16,4 @@ /**

private readonly cache = new Map<string, string>();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
[Symbol.toStringTag]: string;

@@ -18,0 +20,0 @@

@@ -16,3 +16,3 @@ import ApiEndpoints from '../api-endpoints';

DEFAULT_POLL_CONFIG_REQUEST_RETRIES,
DEFAULT_REQUEST_TIMEOUT_MS as DEFAULT_REQUEST_TIMEOUT_MS,
DEFAULT_REQUEST_TIMEOUT_MS,
MAX_EVENT_QUEUE_SIZE,

@@ -24,8 +24,14 @@ POLL_INTERVAL_MS,

import { Evaluator, FlagEvaluation, noneResult } from '../evaluator';
import {
FlagEvaluationDetailsBuilder,
IFlagEvaluationDetails,
} from '../flag-evaluation-details-builder';
import FetchHttpClient from '../http-client';
import {
BanditParameters,
BanditVariation,
BanditParameters,
ConfigDetails,
Flag,
ObfuscatedFlag,
Variation,
VariationType,

@@ -36,4 +42,4 @@ } from '../interfaces';

import {
AttributeType,
Attributes,
AttributeType,
BanditActions,

@@ -47,156 +53,6 @@ BanditSubjectAttributes,

/**
* Client for assigning experiment variations.
* @public
*/
export interface IEppoClient {
/**
* Maps a subject to a variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns a variation value if the subject is part of the experiment sample, otherwise the default value
* @public
*/
getStringAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: string,
): string;
/**
* @deprecated use getBooleanAssignment instead.
*/
getBoolAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: boolean,
): boolean;
/**
* Maps a subject to a boolean variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a boolean variation value if the subject is part of the experiment sample, otherwise the default value
*/
getBooleanAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: boolean,
): boolean;
/**
* Maps a subject to an Integer variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value
*/
getIntegerAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: number,
): number;
/**
* Maps a subject to a Numeric variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value
*/
getNumericAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: number,
): number;
/**
* Maps a subject to a JSON variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a JSON object variation value if the subject is part of the experiment sample, otherwise the default value
*/
getJSONAssignment(
flagKey: string,
subjectKey: string,
subjectAttributes: Attributes,
defaultValue: object,
): object;
/**
* Maps a subject to a string assignment for a given experiment.
* This variation may be a bandit-selected action.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional (can be empty) attributes associated with the subject, for example name and email.
* @param actions possible attributes and their optional (can be empty) attributes to be evaluated by a contextual,
* multi-armed bandit--if one is assigned to the subject.
* @param defaultValue default value to return if the subject is not part of the experiment sample,
* there are no bandit actions, or an error is countered evaluating the feature flag or bandit action */
getBanditAction(
flagKey: string,
subjectKey: string,
subjectAttributes: BanditSubjectAttributes,
actions: BanditActions,
defaultValue: string,
): { variation: string; action: string | null };
/** @Deprecated Renamed to setAssignmentLogger for clarity */
setLogger(logger: IAssignmentLogger): void;
setAssignmentLogger(assignmentLogger: IAssignmentLogger): void;
setBanditLogger(banditLogger: IBanditLogger): void;
useLRUInMemoryAssignmentCache(maxSize: number): void;
useCustomAssignmentCache(cache: AssignmentCache): void;
setConfigurationRequestParameters(
configurationRequestParameters: FlagConfigurationRequestParameters,
): void;
setFlagConfigurationStore(configurationStore: IConfigurationStore<Flag | ObfuscatedFlag>): void;
setBanditVariationConfigurationStore(
banditVariationConfigurationStore: IConfigurationStore<BanditVariation[]>,
): void;
setBanditModelConfigurationStore(
banditModelConfigurationStore: IConfigurationStore<BanditParameters>,
): void;
setIsObfuscated(isObfuscated: boolean): void;
fetchFlagConfigurations(): void;
stopPolling(): void;
setIsGracefulFailureMode(gracefulFailureMode: boolean): void;
getFlagKeys(): string[];
getFlagConfigurations(): Record<string, Flag>;
isInitialized(): boolean;
export interface IAssignmentDetails<T extends Variation['value'] | object> {
variation: T;
action: string | null;
evaluationDetails: IFlagEvaluationDetails;
}

@@ -218,3 +74,3 @@

export default class EppoClient implements IEppoClient {
export default class EppoClient {
private readonly queuedAssignmentEvents: IAssignmentEvent[] = [];

@@ -333,2 +189,13 @@ private assignmentLogger?: IAssignmentLogger;

/**
* Maps a subject to a string variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns a variation value if the subject is part of the experiment sample, otherwise the default value
* @public
*/
public getStringAssignment(

@@ -340,13 +207,41 @@ flagKey: string,

): string {
return (
this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.String(defaultValue),
VariationType.STRING,
).stringValue ?? defaultValue
return this.getStringAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a string variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
public getStringAssignmentDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: Record<string, AttributeType>,
defaultValue: string,
): IAssignmentDetails<string> {
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.String(defaultValue),
VariationType.STRING,
);
return {
variation: eppoValue.stringValue ?? defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* @deprecated use getBooleanAssignment instead.
*/
public getBoolAssignment(

@@ -361,2 +256,11 @@ flagKey: string,

/**
* Maps a subject to a boolean variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a boolean variation value if the subject is part of the experiment sample, otherwise the default value
*/
public getBooleanAssignment(

@@ -368,13 +272,47 @@ flagKey: string,

): boolean {
return (
this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Bool(defaultValue),
VariationType.BOOLEAN,
).boolValue ?? defaultValue
return this.getBooleanAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a boolean variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
public getBooleanAssignmentDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: Record<string, AttributeType>,
defaultValue: boolean,
): IAssignmentDetails<boolean> {
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Bool(defaultValue),
VariationType.BOOLEAN,
);
return {
variation: eppoValue.boolValue ?? defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to an Integer variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns an integer variation value if the subject is part of the experiment sample, otherwise the default value
*/
public getIntegerAssignment(

@@ -386,13 +324,47 @@ flagKey: string,

): number {
return (
this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Numeric(defaultValue),
VariationType.INTEGER,
).numericValue ?? defaultValue
return this.getIntegerAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to an Integer variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
public getIntegerAssignmentDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: Record<string, AttributeType>,
defaultValue: number,
): IAssignmentDetails<number> {
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Numeric(defaultValue),
VariationType.INTEGER,
);
return {
variation: eppoValue.numericValue ?? defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to a numeric variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a number variation value if the subject is part of the experiment sample, otherwise the default value
*/
public getNumericAssignment(

@@ -404,13 +376,47 @@ flagKey: string,

): number {
return (
this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Numeric(defaultValue),
VariationType.NUMERIC,
).numericValue ?? defaultValue
return this.getNumericAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
/**
* Maps a subject to a numeric variation for a given experiment and provides additional details about the
* variation assigned and the reason for the assignment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* The subject attributes are used for evaluating any targeting rules tied to the experiment.
* @returns an object that includes the variation value along with additional metadata about the assignment
* @public
*/
public getNumericAssignmentDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: Record<string, AttributeType>,
defaultValue: number,
): IAssignmentDetails<number> {
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.Numeric(defaultValue),
VariationType.NUMERIC,
);
return {
variation: eppoValue.numericValue ?? defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}
/**
* Maps a subject to a JSON variation for a given experiment.
*
* @param flagKey feature flag identifier
* @param subjectKey an identifier of the experiment subject, for example a user ID.
* @param subjectAttributes optional attributes associated with the subject, for example name and email.
* @param defaultValue default value to return if the subject is not part of the experiment sample
* @returns a JSON object variation value if the subject is part of the experiment sample, otherwise the default value
*/
public getJSONAssignment(

@@ -422,11 +428,24 @@ flagKey: string,

): object {
return (
this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.JSON(defaultValue),
VariationType.JSON,
).objectValue ?? defaultValue
return this.getJSONAssignmentDetails(flagKey, subjectKey, subjectAttributes, defaultValue)
.variation;
}
public getJSONAssignmentDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: Record<string, AttributeType>,
defaultValue: object,
): IAssignmentDetails<object> {
const { eppoValue, flagEvaluationDetails } = this.getAssignmentVariation(
flagKey,
subjectKey,
subjectAttributes,
EppoValue.JSON(defaultValue),
VariationType.JSON,
);
return {
variation: eppoValue.objectValue ?? defaultValue,
action: null,
evaluationDetails: flagEvaluationDetails,
};
}

@@ -440,3 +459,21 @@

defaultValue: string,
): { variation: string; action: string | null } {
): Omit<IAssignmentDetails<string>, 'evaluationDetails'> {
const { variation, action } = this.getBanditActionDetails(
flagKey,
subjectKey,
subjectAttributes,
actions,
defaultValue,
);
return { variation, action };
}
public getBanditActionDetails(
flagKey: string,
subjectKey: string,
subjectAttributes: BanditSubjectAttributes,
actions: BanditActions,
defaultValue: string,
): IAssignmentDetails<string> {
const flagEvaluationDetailsBuilder = this.flagEvaluationDetailsBuilder(flagKey);
const defaultResult = { variation: defaultValue, action: null };

@@ -450,3 +487,9 @@ let variation = defaultValue;

// we don't log a variation or bandit assignment
return defaultResult;
return {
...defaultResult,
evaluationDetails: flagEvaluationDetailsBuilder.buildForNoneResult(
'NO_ACTIONS_SUPPLIED_FOR_BANDIT',
'No bandit actions passed for a flag known to have an active bandit',
),
};
}

@@ -458,3 +501,3 @@

this.ensureNonContextualSubjectAttributes(subjectAttributes);
variation = this.getStringAssignment(
const { variation: _variation, evaluationDetails } = this.getStringAssignmentDetails(
flagKey,

@@ -465,2 +508,3 @@ subjectKey,

);
variation = _variation;

@@ -494,2 +538,4 @@ // Check if the assigned variation is an active bandit

action = banditEvaluation.actionKey;
evaluationDetails.banditAction = action;
evaluationDetails.banditKey = banditKey;

@@ -511,5 +557,7 @@ const banditEvent: IBanditEvent = {

metaData: this.buildLoggerMetadata(),
evaluationDetails,
};
this.logBanditAction(banditEvent);
}
return { variation, action, evaluationDetails };
} catch (err) {

@@ -520,6 +568,10 @@ logger.error('Error evaluating bandit action', err);

}
return defaultResult;
return {
...defaultResult,
evaluationDetails: flagEvaluationDetailsBuilder.buildForNoneResult(
'ASSIGNMENT_ERROR',
`Error evaluating bandit action: ${err.message}`,
),
};
}
return { variation, action };
}

@@ -624,3 +676,3 @@

expectedVariationType: VariationType,
): EppoValue {
): { eppoValue: EppoValue; flagEvaluationDetails: IFlagEvaluationDetails } {
try {

@@ -635,8 +687,24 @@ const result = this.getAssignmentDetail(

if (!result.variation) {
return defaultValue;
return {
eppoValue: defaultValue,
flagEvaluationDetails: result.flagEvaluationDetails,
};
}
return EppoValue.valueOf(result.variation.value, expectedVariationType);
return {
eppoValue: EppoValue.valueOf(result.variation.value, expectedVariationType),
flagEvaluationDetails: result.flagEvaluationDetails,
};
} catch (error) {
return this.rethrowIfNotGraceful(error, defaultValue);
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue);
const flagEvaluationDetails = new FlagEvaluationDetailsBuilder(
'',
[],
'',
'',
).buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`);
return {
eppoValue,
flagEvaluationDetails,
};
}

@@ -674,2 +742,4 @@ }

const flagEvaluationDetailsBuilder = this.flagEvaluationDetailsBuilder(flagKey);
const configDetails = this.getConfigDetails();
const flag = this.getFlag(flagKey);

@@ -680,3 +750,7 @@

// note: this is different from the Python SDK, which returns None instead
return noneResult(flagKey, subjectKey, subjectAttributes);
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult(
'FLAG_UNRECOGNIZED_OR_DISABLED',
`Unrecognized or disabled flag: ${flagKey}`,
);
return noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}

@@ -693,3 +767,7 @@

// note: this is different from the Python SDK, which returns None instead
return noneResult(flagKey, subjectKey, subjectAttributes);
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult(
'FLAG_UNRECOGNIZED_OR_DISABLED',
`Unrecognized or disabled flag: ${flagKey}`,
);
return noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}

@@ -699,5 +777,7 @@

flag,
configDetails,
subjectKey,
subjectAttributes,
this.isObfuscated,
expectedVariationType,
);

@@ -710,3 +790,9 @@ if (this.isObfuscated) {

if (result?.variation && !checkValueTypeMatch(expectedVariationType, result.variation.value)) {
return noneResult(flagKey, subjectKey, subjectAttributes);
const { key: vKey, value: vValue } = result.variation;
const reason = `Expected variation type ${expectedVariationType} does not match for variation '${vKey}' with value ${vValue}`;
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult(
'TYPE_MISMATCH',
reason,
);
return noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails);
}

@@ -725,7 +811,25 @@

private flagEvaluationDetailsBuilder(flagKey: string): FlagEvaluationDetailsBuilder {
const flag = this.getFlag(flagKey);
const configDetails = this.getConfigDetails();
return new FlagEvaluationDetailsBuilder(
configDetails.configEnvironment.name,
flag?.allocations ?? [],
configDetails.configFetchedAt,
configDetails.configPublishedAt,
);
}
private getConfigDetails(): ConfigDetails {
return {
configFetchedAt: this.flagConfigurationStore.getConfigFetchedAt() ?? '',
configPublishedAt: this.flagConfigurationStore.getConfigPublishedAt() ?? '',
configEnvironment: this.flagConfigurationStore.getEnvironment() ?? { name: '' },
};
}
private getFlag(flagKey: string): Flag | null {
if (this.isObfuscated) {
return this.getObfuscatedFlag(flagKey);
}
return this.flagConfigurationStore.get(flagKey);
return this.isObfuscated
? this.getObfuscatedFlag(flagKey)
: this.flagConfigurationStore.get(flagKey);
}

@@ -832,2 +936,3 @@

metaData: this.buildLoggerMetadata(),
evaluationDetails: result.flagEvaluationDetails,
};

@@ -834,0 +939,0 @@

import { IConfigurationStore } from './configuration-store/configuration-store';
import { IHttpClient } from './http-client';
import { BanditVariation, BanditParameters, Flag } from './interfaces';
import { BanditVariation, BanditParameters, Flag, Environment } from './interfaces';
type Entry = Flag | BanditVariation[] | BanditParameters;
// Requests AND stores flag configurations

@@ -22,3 +24,8 @@ export default class ConfigurationRequestor {

await this.flagConfigurationStore.setEntries(configResponse.flags);
await this.hydrateConfigurationStore(this.flagConfigurationStore, {
entries: configResponse.flags,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
const flagsHaveBandits = Object.keys(configResponse.bandits ?? {}).length > 0;

@@ -31,3 +38,9 @@ const banditStoresProvided = Boolean(

const banditVariations = this.indexBanditVariationsByFlagKey(configResponse.bandits);
this.banditVariationConfigurationStore?.setEntries(banditVariations);
await this.hydrateConfigurationStore(this.banditVariationConfigurationStore, {
entries: banditVariations,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
// TODO: different polling intervals for bandit parameters

@@ -39,3 +52,8 @@ const banditResponse = await this.httpClient.getBanditParameters();

}
await this.banditModelConfigurationStore.setEntries(banditResponse.bandits);
await this.hydrateConfigurationStore(this.banditModelConfigurationStore, {
entries: banditResponse.bandits,
environment: configResponse.environment,
createdAt: configResponse.createdAt,
});
}

@@ -45,2 +63,20 @@ }

private async hydrateConfigurationStore<T extends Entry>(
configurationStore: IConfigurationStore<T> | null,
response: {
entries: Record<string, T>;
environment: Environment;
createdAt: string;
},
): Promise<void> {
if (configurationStore) {
const didUpdate = await configurationStore.setEntries(response.entries);
if (didUpdate) {
configurationStore.setEnvironment(response.environment);
configurationStore.setConfigFetchedAt(new Date().toISOString());
configurationStore.setConfigPublishedAt(response.createdAt);
}
}
}
private indexBanditVariationsByFlagKey(

@@ -47,0 +83,0 @@ banditVariationsByBanditKey: Record<string, BanditVariation[]>,

@@ -0,1 +1,3 @@

import { Environment } from '../interfaces';
/**

@@ -30,3 +32,9 @@ * ConfigurationStore interface

isExpired(): Promise<boolean>;
setEntries(entries: Record<string, T>): Promise<void>;
setEntries(entries: Record<string, T>): Promise<boolean>;
setEnvironment(environment: Environment): void;
getEnvironment(): Environment | null;
getConfigFetchedAt(): string | null;
setConfigFetchedAt(configFetchedAt: string): void;
getConfigPublishedAt(): string | null;
setConfigPublishedAt(configPublishedAt: string): void;
}

@@ -33,0 +41,0 @@

import { logger, loggerPrefix } from '../application-logger';
import { Environment } from '../interfaces';

@@ -10,2 +11,5 @@ import { IAsyncStore, IConfigurationStore, ISyncStore } from './configuration-store';

) {}
private environment: Environment | null = null;
private configFetchedAt: string | null = null;
private configPublishedAt: string | null = null;

@@ -61,3 +65,3 @@ /**

public async setEntries(entries: Record<string, T>): Promise<void> {
public async setEntries(entries: Record<string, T>): Promise<boolean> {
if (this.persistentStore) {

@@ -68,3 +72,28 @@ // Persistence store is now initialized and should mark itself accordingly.

this.servingStore.setEntries(entries);
return true;
}
setEnvironment(environment: Environment): void {
this.environment = environment;
}
getEnvironment(): Environment | null {
return this.environment;
}
public getConfigFetchedAt(): string | null {
return this.configFetchedAt;
}
public setConfigFetchedAt(configFetchedAt: string): void {
this.configFetchedAt = configFetchedAt;
}
public getConfigPublishedAt(): string | null {
return this.configPublishedAt;
}
public setConfigPublishedAt(configPublishedAt: string): void {
this.configPublishedAt = configPublishedAt;
}
}

@@ -0,1 +1,3 @@

import { Environment } from '../interfaces';
import { IConfigurationStore, ISyncStore } from './configuration-store';

@@ -32,2 +34,5 @@

private initialized = false;
private configFetchedAt: string | null = null;
private configPublishedAt: string | null = null;
private environment: Environment | null = null;

@@ -59,6 +64,31 @@ init(): Promise<void> {

async setEntries(entries: Record<string, T>): Promise<void> {
async setEntries(entries: Record<string, T>): Promise<boolean> {
this.servingStore.setEntries(entries);
this.initialized = true;
return true;
}
public getEnvironment(): Environment | null {
return this.environment;
}
public setEnvironment(environment: Environment): void {
this.environment = environment;
}
public getConfigFetchedAt(): string | null {
return this.configFetchedAt;
}
public setConfigFetchedAt(configFetchedAt: string): void {
this.configFetchedAt = configFetchedAt;
}
public getConfigPublishedAt(): string | null {
return this.configPublishedAt;
}
public setConfigPublishedAt(configPublishedAt: string): void {
this.configPublishedAt = configPublishedAt;
}
}

@@ -1,2 +0,17 @@

import { Flag, Shard, Range, Variation } from './interfaces';
import {
AllocationEvaluation,
AllocationEvaluationCode,
IFlagEvaluationDetails,
FlagEvaluationDetailsBuilder,
} from './flag-evaluation-details-builder';
import {
Flag,
Shard,
Range,
Variation,
Allocation,
Split,
VariationType,
ConfigDetails,
} from './interfaces';
import { Rule, matchesRule } from './rules';

@@ -14,2 +29,3 @@ import { MD5Sharder, Sharder } from './sharders';

doLog: boolean;
flagEvaluationDetails: IFlagEvaluationDetails;
}

@@ -26,18 +42,53 @@

flag: Flag,
configDetails: ConfigDetails,
subjectKey: string,
subjectAttributes: Attributes,
obfuscated: boolean,
expectedVariationType?: VariationType,
): FlagEvaluation {
const flagEvaluationDetailsBuilder = new FlagEvaluationDetailsBuilder(
configDetails.configEnvironment.name,
flag.allocations,
configDetails.configFetchedAt,
configDetails.configPublishedAt,
);
if (!flag.enabled) {
return noneResult(flag.key, subjectKey, subjectAttributes);
return noneResult(
flag.key,
subjectKey,
subjectAttributes,
flagEvaluationDetailsBuilder.buildForNoneResult(
'FLAG_UNRECOGNIZED_OR_DISABLED',
`Unrecognized or disabled flag: ${flag.key}`,
),
);
}
const now = new Date();
for (const allocation of flag.allocations) {
if (allocation.startAt && now < new Date(allocation.startAt)) continue;
if (allocation.endAt && now > new Date(allocation.endAt)) continue;
const unmatchedAllocations: Array<AllocationEvaluation> = [];
for (let i = 0; i < flag.allocations.length; i++) {
const allocation = flag.allocations[i];
const addUnmatchedAllocation = (code: AllocationEvaluationCode) => {
unmatchedAllocations.push({
key: allocation.key,
allocationEvaluationCode: code,
orderPosition: i + 1,
});
};
if (
matchesRules(allocation?.rules ?? [], { id: subjectKey, ...subjectAttributes }, obfuscated)
) {
if (allocation.startAt && now < new Date(allocation.startAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.BEFORE_START_TIME);
continue;
}
if (allocation.endAt && now > new Date(allocation.endAt)) {
addUnmatchedAllocation(AllocationEvaluationCode.AFTER_END_TIME);
continue;
}
const { matched, matchedRule } = matchesRules(
allocation?.rules ?? [],
{ id: subjectKey, ...subjectAttributes },
obfuscated,
);
if (matched) {
for (const split of allocation.splits) {

@@ -47,2 +98,16 @@ if (

) {
const variation = flag.variations[split.variationKey];
const flagEvaluationDetails = flagEvaluationDetailsBuilder
.setMatch(
i,
variation,
allocation,
matchedRule,
unmatchedAllocations,
expectedVariationType,
)
.build(
'MATCH',
this.getMatchedEvaluationDetailsMessage(allocation, split, subjectKey),
);
return {

@@ -53,12 +118,26 @@ flagKey: flag.key,

allocationKey: allocation.key,
variation: flag.variations[split.variationKey],
variation,
extraLogging: split.extraLogging ?? {},
doLog: allocation.doLog,
flagEvaluationDetails,
};
}
}
// matched, but does not fall within split range
addUnmatchedAllocation(AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS);
} else {
addUnmatchedAllocation(AllocationEvaluationCode.FAILING_RULE);
}
}
return noneResult(flag.key, subjectKey, subjectAttributes);
return noneResult(
flag.key,
subjectKey,
subjectAttributes,
flagEvaluationDetailsBuilder
.setNoMatchFound(unmatchedAllocations)
.build(
'DEFAULT_ALLOCATION_NULL',
'No allocations matched. Falling back to "Default Allocation", serving NULL',
),
);
}

@@ -70,2 +149,21 @@

}
private getMatchedEvaluationDetailsMessage = (
allocation: Allocation,
split: Split,
subjectKey: string,
): string => {
const hasDefinedRules = !!allocation.rules?.length;
const isExperiment = allocation.splits.length > 1;
const isPartialRollout = split.shards.length > 1;
const isExperimentOrPartialRollout = isExperiment || isPartialRollout;
if (hasDefinedRules && isExperimentOrPartialRollout) {
return `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`;
}
if (hasDefinedRules && !isExperimentOrPartialRollout) {
return `Supplied attributes match rules defined in allocation "${allocation.key}".`;
}
return `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`;
};
}

@@ -85,2 +183,3 @@

subjectAttributes: Attributes,
flagEvaluationDetails: IFlagEvaluationDetails,
): FlagEvaluation {

@@ -95,2 +194,3 @@ return {

doLog: false,
flagEvaluationDetails,
};

@@ -103,4 +203,26 @@ }

obfuscated: boolean,
): boolean {
return !rules.length || rules.some((rule) => matchesRule(rule, subjectAttributes, obfuscated));
): { matched: boolean; matchedRule: Rule | null } {
if (!rules.length) {
return {
matched: true,
matchedRule: null,
};
}
let matchedRule: Rule | null = null;
const hasMatch = rules.some((rule) => {
const matched = matchesRule(rule, subjectAttributes, obfuscated);
if (matched) {
matchedRule = rule;
}
return matched;
});
return hasMatch
? {
matched: true,
matchedRule,
}
: {
matched: false,
matchedRule: null,
};
}
import ApiEndpoints from './api-endpoints';
import { BanditVariation, BanditParameters, Flag } from './interfaces';
import { BanditParameters, BanditVariation, Environment, Flag } from './interfaces';

@@ -20,2 +20,4 @@ export interface IQueryParams {

export interface IUniversalFlagConfigResponse {
createdAt: string; // ISO formatted string
environment: Environment;
flags: Record<string, Flag>;

@@ -22,0 +24,0 @@ bandits: Record<string, BanditVariation[]>;

@@ -18,3 +18,6 @@ import ApiEndpoints from './api-endpoints';

} from './cache/abstract-assignment-cache';
import EppoClient, { FlagConfigurationRequestParameters, IEppoClient } from './client/eppo-client';
import EppoClient, {
FlagConfigurationRequestParameters,
IAssignmentDetails,
} from './client/eppo-client';
import FlagConfigRequestor from './configuration-requestor';

@@ -43,2 +46,3 @@ import {

AbstractAssignmentCache,
IAssignmentDetails,
IAssignmentHooks,

@@ -50,3 +54,2 @@ IAssignmentLogger,

EppoClient,
IEppoClient,
constants,

@@ -53,0 +56,0 @@ ApiEndpoints,

@@ -41,2 +41,12 @@ import { Rule } from './rules';

export interface Environment {
name: string;
}
export interface ConfigDetails {
configFetchedAt: string;
configPublishedAt: string;
configEnvironment: Environment;
}
export interface Flag {

@@ -53,2 +63,3 @@ key: string;

key: string;
environment: Environment;
enabled: boolean;

@@ -55,0 +66,0 @@ variationType: VariationType;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc