/* eslint-disable camelcase */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ // Based on https://github.com/dobesv/PlanOut.js/blob/types/types/index.d.ts declare module 'planout' { export interface Inputs { [key: string]: string; } export type ParamValue = string | number | boolean | null | undefined; export type Params = { [key: string]: ParamValue; }; export interface PlanoutEvent { event: string; name: string; salt: string; inputs: Inputs; params: Params; } export interface PlanoutNamespaceConfig { name: string; num_segments: number; primary_unit: string; compiled: PlanoutCode; } export interface PlanoutExperimentConfig { name: string; namespace_name: string; num_segments: number; compiled: PlanoutCode; } export interface PlanoutNamespaceOp { op: 'add' | 'remove'; experiment: PlanoutExperimentConfig; } export interface PlanoutConfig { namespaces: PlanoutNamespaceConfig[], experiments: PlanoutNamespaceOp[], } export class Assignment { constructor(experimentSalt: string, overrides?: Partial); evaluate(value: unknown): unknown; getOverrides(): Partial; /** * Set a value as an "override" - this value will not be replaced by calls * to `set`. */ addOverride(key: K, value: ParamsType[K]): void; /** * Set all the overrides (fixed values) at once */ setOverrides(overrides: Partial): void; /** * Set a value * * If a literal value is provided, it is used as is. * * If a PlanOutOpRandom instance is passed, this calls `execute(this)` on * it to calculate the value to store. * * If the value was previously set as an override, this will not replace * the existing value. * * @param key Name of the parameter * @param value Value or calculation */ set( key: K, value: ParamsType[K] | Ops.Base.PlanOutOp, ): void; /** * Get a parameter value. If the value would have been null or undefined, * `def` is returned. */ get(name: K, def?: ParamsType[K]): ParamsType[K]; /** * Get all the parameter values set so far */ getParams(): Partial; /** * Remove a parameter value */ del(name: string): void; /** * Number of params set so far */ length(): number; } export class BaseExperiment { constructor(inputs: InputsType); } export class Experiment extends BaseExperiment { protected _exposureLogged: boolean; protected _inExperiment: boolean; protected inputs?: InputsType; public _assigned: boolean; public _assignment: Assignment>; /** * Subclasses can use this implementation of getParamNames to calculate * the parameter names by examining the source code of `assign` at runtime. */ getDefaultParamNames(): string[]; requireAssignment(): void; requireExposureLogging(): void; /** * Called by the constructor to do any additional setup the subclass * wants to do. * * This should at a minimum call `this.setName('')`. * * Experiment names should be chosen to be unique - there should not be * two experiments with the same name running at similar times, as this * will confuse this library and also make it harder to distinguish the * results of different experiments. * * @abstract */ setup(): void; /** * True if this experiment is active */ inExperiment(): boolean; /** * Override a parameter, preventing it from being randomly selected */ addOverride(key: string, value: string): void; /** * Replace all overrides */ setOverrides(obj: Partial & Partial): void; getSalt(): string; setSalt(value: string): void; getName(): string; /** * This is where you define the calculation of the parameters of the * experiment. You call params.set(, ) * * @abstract * @param params Object to receive parameter definitions * @param args Constructor argument */ assign(params: Assignment, args: InputsType): boolean | void; /** * Get the parameter names for this experiment. This is a list of names * that are calculated / defined by the experiment, which you can fetch * using "get". * * @abstract */ getParamNames(): string[]; shouldFetchExperimentParameter(name: string): boolean; /** * Call this in your setup implementation to set the experiment name */ setName(name: string): void; setAutoExposureLogging(b: boolean): void; getParams(): ParamsType; get( name: K, def: ParamsType[K], ): ParamsType[K]; toString(): string; logExposure(extras: { [k: string]: any }): void; shouldLogExposure(paramName: string): boolean; logEvent(eventType: string, extras: { [k: string]: any }): void; /** * Called to configure logging when an experiment starts up. * * @abstract */ configureLogger(): void; /** * Log a message to the analytics system * * @abstract * @param data */ log(data: { event?: string; name?: string; time?: number; salt?: string; inputs?: InputsType; params?: ParamsType; extra_data?: { [k: string]: any }; }): void; /** * Return whether exposure to this experiment was already logged * * Suggested implementation is to return this._exposureLogged in client * side code, but you might also want to cache this information in * localStorage or somesuch to avoid repeated logging of the exposure. * * @abstract */ previouslyLogged(): boolean; } export namespace ExperimentSetup { const registerExperimentInput: ( key: string, value: string | number | boolean | null | undefined, experimentName?: string | null, ) => void; const getExperimentInputs: (experimentName: string) => any; } export interface PlanoutCodeLiteralOp { op: 'literal'; value: any; } export interface PlanoutCodeGetOp { op: 'get'; var: string; } export interface PlanoutCodeSetOp { op: 'set'; var: string; value: PlanoutCode; } export interface PlanoutCodeSeqOp { op: 'seq'; seq: PlanoutCode[]; } export interface PlanoutCodeCondOp { op: 'cond'; cond: Array<{ if: PlanoutCode; then: PlanoutCode }>; } export interface PlanoutCodeBinaryOp { op: 'equals' | 'and' | 'or' | '>' | '<' | '>=' | '<=' | '%' | '/'; left: PlanoutCode; } export interface PlanoutCodeUnaryOp { op: 'return' | 'not' | 'round' | '-' | 'length'; value: PlanoutCode; } export interface PlanoutCodeCommutativeOp { op: 'min' | 'max' | 'product' | 'sum'; values: PlanoutCode[]; } export type PlanoutCodeValue = string | number | boolean | null; export interface PlanoutCodeRandomRangeOp { op: 'randomFloat' | 'randomInteger'; min: PlanoutCode; max: PlanoutCode; } export interface PlanoutCodeRandomTrialOp { op: 'bernoulliTrial'; p: PlanoutCode; } export interface PlanoutCodeRandomFilterOp { op: 'bernoulliFilter'; p: PlanoutCode; choices: PlanoutCode[]; } export interface PlanoutCodeUniformChoiceOp { op: 'uniformChoice'; choices: PlanoutCode[]; } export interface PlanoutCodeWeightedChoiceOp { op: 'weightedChoice'; choices: PlanoutCode[]; } export interface PlanoutCodeSampleOp { op: 'sample'; choices: PlanoutCode[]; draws: PlanoutCode; } export type PlanoutCodeRandomOp = { unit: PlanoutCode } & ( | PlanoutCodeRandomRangeOp | PlanoutCodeRandomTrialOp | PlanoutCodeRandomFilterOp | PlanoutCodeUniformChoiceOp | PlanoutCodeWeightedChoiceOp | PlanoutCodeSampleOp ); export type PlanoutCodeCoreOp = | PlanoutCodeGetOp | PlanoutCodeSetOp | PlanoutCodeSeqOp | PlanoutCodeCondOp | PlanoutCodeLiteralOp | PlanoutCodeBinaryOp | PlanoutCodeUnaryOp | PlanoutCodeCommutativeOp; type PlanoutCodeUnit = | PlanoutCodeValue | PlanoutCodeCoreOp | PlanoutCodeRandomOp; export type PlanoutCode = PlanoutCodeUnit | PlanoutCodeUnit[]; /** * Execute some PlanoutCode AST * * Values that are set in the code are stored in the "env" inside the * interpreter, and can be fetched using "get". "get" also returns * values from the input. */ export class Interpreter extends Object { name: string; _inExperiment?: boolean; /** * Run some planout code * * @param serialization Code to execute to update the environment * @param experimentSalt Salt to use for "random" values * @param inputs Values to provide initially as variables in the script * @param environment Environment to use; if not provided, one will be created */ constructor( serialization: PlanoutCode, experimentSalt?: string, inputs?: InputsType, environment?: Assignment, ); inExperiment(): boolean; setEnv(newEnv: Assignment): Interpreter; has(name: string): boolean; get(name: K, def: ParamsType[K]): ParamsType[K]; getParams(): ParamsType; set( key: K, value: ParamsType[K] | Ops.Base.PlanOutOp, ): Interpreter; getSaltSeparator(): string; setOverrides(overrides: Partial): void; getOverrides(): Partial; hasOverride(name: string): boolean; registerCustomOperators(operators: unknown): void; evaluate(code: PlanoutCode): any; } export namespace Namespace { class Namespace { public numSegments: number; // Add an experiment; returns false if the experiment could not be added addExperiment( name: string, obj: BaseExperiment, segments: number, ): boolean; removeExperiment(name: string): void; setAutoExposureLogging(value: boolean): void; inExperiment(): boolean; get(name: string, def: ParamsType[K]): ParamsType[K]; logExposure(extras: object): void; logEvent(eventType: string, extras: object): void; requireExperiment(): void; requireDefaultExperiment(): void; } class SimpleNamespace extends Namespace { constructor(args: InputsType); inputs: InputsType; _experiment?: Experiment; _defaultExperiment?: Experiment; _assignExperiment(): void; _assignExperimentObject(name: string): void; currentExperiments: { [key: string]: BaseExperiment }; setupDefaults(): void; setup(): void; setupExperiments(): void; getPrimaryUnit(): string; allowedOverride(): boolean; getOverrides(): Partial; setPrimaryUnit(value: string): void; getSegment(): number; defaultGet( name: K, def: ParamsType[K], ): ParamsType[K]; getName(): string; setName(name: string): void; previouslyLogged(): boolean; inExperiment(): boolean; setGlobalOverride(name: string): void; setLocalOverride(name: string): void; getParams(experimentName: string): ParamsType | null; getOriginalExperimentName(): string | null; } } type PlanOutOpMapper = Assignment | Interpreter; interface PlanOutOp { execute(assignment: PlanOutOp): T; } export namespace Ops { namespace Core { class Literal extends Ops.Base.PlanOutOp<{ value: T }, T> {} class Get extends Ops.Base.PlanOutOp<{ var: string }, string> {} class Seq extends Ops.Base.PlanOutOp<{ seq: T[] }, T[]> {} class Set< T extends string | number | Ops.Base.PlanOutOp > extends Ops.Base.PlanOutOp<{ var: string; value: T }, void> {} class Arr extends Ops.Base.PlanOutOp<{ values: T[] }, T[]> {} class Map extends Ops.Base.PlanOutOp {} class Coalesce extends Ops.Base.PlanOutOp<{ values: T[] }, T> {} class Index extends Ops.Base.PlanOutOp< { base: T; index: keyof T }, T > {} class Cond extends Ops.Base.PlanOutOp< { cond: Array<{ if: any; then: T }> }, T > {} class And extends Ops.Base.PlanOutOp<{ values: T[] }, boolean> {} class Or extends Ops.Base.PlanOutOp<{ values: T[] }, boolean> {} class Product extends Ops.Base.PlanOutOpCommutative {} class Sum extends Ops.Base.PlanOutOpCommutative {} class Equals extends Ops.Base.PlanOutOpBinary {} class GreaterThan extends Ops.Base.PlanOutOpBinary {} class LessThan extends Ops.Base.PlanOutOpBinary {} class LessThanOrEqualTo extends Ops.Base.PlanOutOpBinary< number, boolean > {} class GreaterThanOrEqualTo extends Ops.Base.PlanOutOpBinary< number, boolean > {} class Mod extends Ops.Base.PlanOutOpBinary {} class Divide extends Ops.Base.PlanOutOpBinary {} class Round extends Ops.Base.PlanOutOpUnary {} class Not extends Ops.Base.PlanOutOpUnary {} class Negative extends Ops.Base.PlanOutOpUnary {} class Min extends Ops.Base.PlanOutOpCommutative {} class Max extends Ops.Base.PlanOutOpCommutative {} class Length extends Ops.Base.PlanOutOpUnary< T, T['length'] > {} class Return extends Ops.Base.PlanOutOp<{ value: T }, never> {} } namespace Random { type UnitType = null | string | number | Array; class PlanOutOpRandom extends Ops.Base .PlanOutOpSimple< InputsType & { unit: UnitType; }, ValueT > {} // Pick `draws` elements at random from `choices` class Sample extends PlanOutOpRandom< { choices: T[]; draws: number }, T[] > {} // Pick one element at random from `choices`; weights make the selection // uneven class WeightedChoice extends PlanOutOpRandom< { choices: T[]; weights: number[] }, T > {} // Pick one element at random from `choices`, all choices equally likely class UniformChoice extends PlanOutOpRandom<{ choices: T[] }, T> {} class BernoulliFilter extends PlanOutOpRandom< { p: number; choices: T[] }, T[] > {} class BernoulliTrial extends PlanOutOpRandom<{ p: number }, 0 | 1> {} class RandomInteger extends PlanOutOpRandom< { min: number; max: number }, number > {} class RandomFloat extends PlanOutOpRandom< { min: number; max: number }, number > {} } namespace Base { class PlanOutOp { constructor(args: ArgsT); execute(mapper: PlanOutOpMapper): ValueT; getArgMixed(name: K): ArgsT[K]; getArgNumber>(name: K): number; getArgString>(name: K): string; getArgList>(name: K): any[]; getArgObject>(name: K): object; getArgIndexish>( name: K, ): object | any[]; } class PlanOutOpSimple< ArgsT, ValueT, OtherParamsT = unknown > extends PlanOutOp { simpleExecute(): unknown; } class PlanOutOpUnary extends PlanOutOpSimple< { value: ArgT }, ValueT > {} class PlanOutOpBinary extends PlanOutOpSimple< { left: T; right: T }, U > {} class PlanOutOpCommutative extends PlanOutOpSimple< { left: T; right: T }, T > {} } } }