@optimizely/optimizely-sdk
Version:
JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts
287 lines (286 loc) • 18.2 kB
TypeScript
/**
* Copyright 2017-2022, 2024-2026, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LoggerFacade } from '../../logging/logger';
import { DecisionSource } from '../../utils/enums';
import { ProjectConfig } from '../../project_config/project_config';
import { DecisionResponse, Experiment, FeatureFlag, Holdout, OptimizelyDecideOption, OptimizelyUserContext, UserProfileService, UserProfileServiceAsync, Variation } from '../../shared_types';
import { CmabService } from './cmab/cmab_service';
import { OpType } from '../../utils/type';
import { Value } from '../../utils/promise/operation_value';
import { Platform } from '../../platform_support';
export declare const EXPERIMENT_NOT_RUNNING = "Experiment %s is not running.";
export declare const RETURNING_STORED_VARIATION = "Returning previously activated variation \"%s\" of experiment \"%s\" for user \"%s\" from user profile.";
export declare const USER_NOT_IN_EXPERIMENT = "User %s does not meet conditions to be in experiment %s.";
export declare const USER_HAS_NO_VARIATION = "User %s is in no variation of experiment %s.";
export declare const USER_HAS_VARIATION = "User %s is in variation %s of experiment %s.";
export declare const USER_FORCED_IN_VARIATION = "User %s is forced in variation %s.";
export declare const FORCED_BUCKETING_FAILED = "Variation key %s is not in datafile. Not activating user %s.";
export declare const EVALUATING_AUDIENCES_COMBINED = "Evaluating audiences for %s \"%s\": %s.";
export declare const AUDIENCE_EVALUATION_RESULT_COMBINED = "Audiences for %s %s collectively evaluated to %s.";
export declare const USER_IN_ROLLOUT = "User %s is in rollout of feature %s.";
export declare const USER_NOT_IN_ROLLOUT = "User %s is not in rollout of feature %s.";
export declare const FEATURE_HAS_NO_EXPERIMENTS = "Feature %s is not attached to any experiments.";
export declare const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = "User %s does not meet conditions for targeting rule %s.";
export declare const USER_NOT_BUCKETED_INTO_TARGETING_RULE = "User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.";
export declare const USER_BUCKETED_INTO_TARGETING_RULE = "User %s bucketed into targeting rule %s.";
export declare const NO_ROLLOUT_EXISTS = "There is no rollout of feature %s.";
export declare const INVALID_ROLLOUT_ID = "Invalid rollout ID %s attached to feature %s";
export declare const ROLLOUT_HAS_NO_EXPERIMENTS = "Rollout of feature %s has no experiments";
export declare const IMPROPERLY_FORMATTED_EXPERIMENT = "Experiment key %s is improperly formatted.";
export declare const USER_HAS_FORCED_VARIATION = "Variation %s is mapped to experiment %s and user %s in the forced variation map.";
export declare const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = "User %s meets conditions for targeting rule %s.";
export declare const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = "Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.";
export declare const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = "Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.";
export declare const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = "Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.";
export declare const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = "Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.";
export declare const CMAB_NOT_SUPPORTED_IN_SYNC = "CMAB is not supported in sync mode.";
export declare const CMAB_FETCH_FAILED = "Failed to fetch CMAB data for experiment %s.";
export declare const CMAB_FETCHED_VARIATION_INVALID = "Fetched variation %s for cmab experiment %s is invalid.";
export declare const HOLDOUT_NOT_RUNNING = "Holdout %s is not running.";
export declare const USER_MEETS_CONDITIONS_FOR_HOLDOUT = "User %s meets conditions for holdout %s.";
export declare const USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT = "User %s does not meet conditions for holdout %s.";
export declare const USER_BUCKETED_INTO_HOLDOUT_VARIATION = "User %s is in variation %s of holdout %s.";
export declare const USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION = "User %s is in no holdout variation.";
export interface DecisionObj {
experiment: Experiment | Holdout | null;
variation: Variation | null;
decisionSource: DecisionSource;
cmabUuid?: string;
}
interface DecisionServiceOptions {
userProfileService?: UserProfileService;
userProfileServiceAsync?: UserProfileServiceAsync;
logger?: LoggerFacade;
UNSTABLE_conditionEvaluators: unknown;
cmabService: CmabService;
}
type VarationKeyWithCmabParams = {
variationKey?: string;
cmabUuid?: string;
};
export type DecisionReason = [string, ...any[]];
export type VariationResult = DecisionResponse<VarationKeyWithCmabParams>;
export type DecisionResult = DecisionResponse<DecisionObj>;
export type DecideOptionsMap = Partial<Record<OptimizelyDecideOption, boolean>>;
export declare const CMAB_DUMMY_ENTITY_ID = "$";
export declare const LOGGER_NAME = "DecisionService";
/**
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to.
*
* The decision service contains all logic around how a user decision is made. This includes all of the following (in order):
* 1. Checking experiment status
* 2. Checking forced bucketing
* 3. Checking whitelisting
* 4. Checking user profile service for past bucketing decisions (sticky bucketing)
* 5. Checking audience targeting
* 6. Using Murmurhash3 to bucket the user.
*
* @constructor
* @param {DecisionServiceOptions} options
* @returns {DecisionService}
*/
export declare class DecisionService {
private logger?;
private audienceEvaluator;
private forcedVariationMap;
private userProfileService?;
private userProfileServiceAsync?;
private cmabService;
constructor(options: DecisionServiceOptions);
private isCmab;
/**
* Resolves the variation into which the visitor will be bucketed.
*
* @param {ProjectConfig} configObj - The parsed project configuration object.
* @param {Experiment} experiment - The experiment for which the variation is being resolved.
* @param {OptimizelyUserContext} user - The user context associated with this decision.
* @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into,
* along with the decision reasons.
*/
private resolveVariation;
private getDecisionForCmabExperiment;
private getDecisionFromBucketer;
/**
* Gets variation where visitor will be bucketed.
* @param {ProjectConfig} configObj The parsed project configuration object
* @param {Experiment} experiment
* @param {OptimizelyUserContext} user A user context
* @param {[key: string]: boolean} options Optional map of decide options
* @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into
* and the decide reasons.
*/
getVariation(configObj: ProjectConfig, experiment: Experiment, user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResponse<string | null>;
/**
* Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService
* @param {string} userId
* @param {UserAttributes} attributes
* @return {ExperimentBucketMap} finalized copy of experiment_bucket_map
*/
private resolveExperimentBucketMap;
/**
* Checks if user is whitelisted into any variation and return that variation if so
* @param {Experiment} experiment
* @param {string} userId
* @return {DecisionResponse<Variation|null>} DecisionResponse containing the forced variation if it exists
* or user ID and the decide reasons.
*/
private getWhitelistedVariation;
/**
* Checks whether the user is included in experiment audience
* @param {ProjectConfig} configObj The parsed project configuration object
* @param {string} experimentKey Key of experiment being validated
* @param {string} evaluationAttribute String representing experiment key or rule
* @param {string} userId ID of user
* @param {UserAttributes} attributes Optional parameter for user's attributes
* @param {string} loggingKey String representing experiment key or rollout rule. To be used in log messages only.
* @return {DecisionResponse<boolean>} DecisionResponse DecisionResponse containing result true if user meets audience conditions and
* the decide reasons.
*/
private checkIfUserIsInAudience;
/**
* Given an experiment key and user ID, returns params used in bucketer call
* @param {ProjectConfig} configObj The parsed project configuration object
* @param {string} experimentKey Experiment key used for bucketer
* @param {string} bucketingId ID to bucket user into
* @param {string} userId ID of user to be bucketed
* @return {BucketerParams}
*/
private buildBucketerParams;
/**
* Determines if a user should be bucketed into a holdout variation.
* @param {ProjectConfig} configObj - The parsed project configuration object.
* @param {Holdout} holdout - The holdout to evaluate.
* @param {OptimizelyUserContext} user - The user context.
* @returns {DecisionResponse<DecisionObj>} - DecisionResponse containing holdout decision and reasons.
*/
private getVariationForHoldout;
/**
* Pull the stored variation out of the experimentBucketMap for an experiment/userId
* @param {ProjectConfig} configObj The parsed project configuration object
* @param {Experiment} experiment
* @param {string} userId
* @param {ExperimentBucketMap} experimentBucketMap mapping experiment => { variation_id: <variationId> }
* @return {Variation|null} the stored variation or null if the user profile does not have one for the given experiment
*/
private getStoredVariation;
/**
* Get the user profile with the given user ID
* @param {string} userId
* @return {UserProfile} the stored user profile or an empty profile if one isn't found or error
*/
private getUserProfile;
private updateUserProfile;
/**
* Saves the bucketing decision to the user profile
* @param {Experiment} experiment
* @param {Variation} variation
* @param {string} userId
* @param {ExperimentBucketMap} experimentBucketMap
*/
private saveUserProfile;
/**
* Determines variations for the specified feature flags.
*
* @param {ProjectConfig} configObj - The parsed project configuration object.
* @param {FeatureFlag[]} featureFlags - The feature flags for which variations are to be determined.
* @param {OptimizelyUserContext} user - The user context associated with this decision.
* @param {Record<string, boolean>} options - An optional map of decision options.
* @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with
* experiment, variation, decisionSource properties, and decision reasons.
*/
getVariationsForFeatureList(configObj: ProjectConfig, featureFlags: FeatureFlag[], user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResult[];
resolveVariationsForFeatureList<OP extends OpType>(op: OP, configObj: ProjectConfig, featureFlags: FeatureFlag[], user: OptimizelyUserContext, options: DecideOptionsMap): Value<OP, DecisionResult[]>;
private resolveVariationForFlag;
/**
* Given a feature, user ID, and attributes, returns a decision response containing
* an object representing a decision and decide reasons. If the user was bucketed into
* a variation for the given feature and attributes, the decision object will have variation and
* experiment properties (both objects), as well as a decisionSource property.
* decisionSource indicates whether the decision was due to a rollout or an
* experiment.
* @param {ProjectConfig} configObj The parsed project configuration object
* @param {FeatureFlag} feature A feature flag object from project configuration
* @param {OptimizelyUserContext} user A user context
* @param {[key: string]: boolean} options Map of decide options
* @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource
* properties and decide reasons. If the user was not bucketed into a variation, the variation
* property in decision object is null.
*/
getVariationForFeature(configObj: ProjectConfig, feature: FeatureFlag, user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResponse<DecisionObj>;
private getVariationForFeatureExperiment;
private traverseFeatureExperimentList;
private getVariationForRollout;
/**
* Get bucketing Id from user attributes.
* @param {string} userId
* @param {UserAttributes} attributes
* @returns {string} Bucketing Id if it is a string type in attributes, user Id otherwise.
*/
private getBucketingId;
/**
* Finds a validated forced decision for specific flagKey and optional ruleKey.
* @param {ProjectConfig} config A projectConfig.
* @param {OptimizelyUserContext} user A Optimizely User Context.
* @param {string} flagKey A flagKey.
* @param {ruleKey} ruleKey A ruleKey (optional).
* @return {DecisionResponse<Variation|null>} DecisionResponse object containing valid variation object and decide reasons.
*/
findValidatedForcedDecision(config: ProjectConfig, user: OptimizelyUserContext, flagKey: string, ruleKey?: string): DecisionResponse<Variation | null>;
/**
* Removes forced variation for given userId and experimentKey
* @param {string} userId String representing the user id
* @param {string} experimentId Number representing the experiment id
* @param {string} experimentKey Key representing the experiment id
* @throws If the user id is not valid or not in the forced variation map
*/
private removeForcedVariation;
/**
* Sets forced variation for given userId and experimentKey
* @param {string} userId String representing the user id
* @param {string} experimentId Number representing the experiment id
* @param {number} variationId Number representing the variation id
* @throws If the user id is not valid
*/
private setInForcedVariationMap;
/**
* Gets the forced variation key for the given user and experiment.
* @param {ProjectConfig} configObj Object representing project configuration
* @param {string} experimentKey Key for experiment.
* @param {string} userId The user Id.
* @return {DecisionResponse<string|null>} DecisionResponse containing variation which the given user and experiment
* should be forced into and the decide reasons.
*/
getForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string): DecisionResponse<string | null>;
/**
* Sets the forced variation for a user in a given experiment
* @param {ProjectConfig} configObj Object representing project configuration
* @param {string} experimentKey Key for experiment.
* @param {string} userId The user Id.
* @param {string|null} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping
* @return {boolean} A boolean value that indicates if the set completed successfully.
*/
setForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string, variationKey: string | null): boolean;
private getVariationFromExperimentRule;
private getVariationFromDeliveryRule;
}
/**
* Creates an instance of the DecisionService.
* @param {DecisionServiceOptions} options Configuration options
* @return {Object} An instance of the DecisionService
*/
export declare function createDecisionService(options: DecisionServiceOptions): DecisionService;
export declare const __platforms: Platform[];
export {};