Skip to content

Commit

Permalink
feat: add js-user-persisted-storage package (#75)
Browse files Browse the repository at this point in the history
* feat: add js-user-persisted-storage package

* feat: add js-user-persisted-storage package

* feat: add ability to fetch values

* chore: split evaluation options into types

* test: add user persisted storage test

* chore: delete client-extensions package

* chore: update readme precomp/on-device

* feat: add persisted user storage example

* refac: remove redundant exposure arg
  • Loading branch information
daniel-statsig committed Mar 18, 2024
1 parent cbc297e commit 63dafc3
Show file tree
Hide file tree
Showing 50 changed files with 1,325 additions and 501 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Statsig helps you move faster with feature gates (feature flags), and/or dynamic

## Variants

- [Precomputed (Recommended)](packages/js-client/README.md)
- [On Device](packages/js-on-device-eval-client/README.md)
- [Precomputed Evaluations (Recommended)](packages/js-client/README.md)
- [On Device Evaluations](packages/js-on-device-eval-client/README.md)

## React Bindings

Expand Down
40 changes: 29 additions & 11 deletions packages/client-core/src/ClientInterfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { EvaluationOptions } from './StatsigClientBase';
import {
DynamicConfigEvaluationOptions,
ExperimentEvaluationOptions,
FeatureGateEvaluationOptions,
LayerEvaluationOptions,
} from './EvaluationOptions';
import { StatsigClientEventEmitterInterface } from './StatsigClientEventEmitter';
import { EvaluationsDataAdapter, SpecsDataAdapter } from './StatsigDataAdapter';
import { StatsigEvent } from './StatsigEvent';
Expand All @@ -20,24 +25,28 @@ export interface OnDeviceEvaluationsInterface
checkGate(
name: string,
user: StatsigUser,
options: EvaluationOptions,
options?: FeatureGateEvaluationOptions,
): boolean;
getFeatureGate(
name: string,
user: StatsigUser,
options: EvaluationOptions,
options?: FeatureGateEvaluationOptions,
): FeatureGate;
getDynamicConfig(
name: string,
user: StatsigUser,
options: EvaluationOptions,
options?: DynamicConfigEvaluationOptions,
): DynamicConfig;
getExperiment(
name: string,
user: StatsigUser,
options: EvaluationOptions,
options?: ExperimentEvaluationOptions,
): Experiment;
getLayer(name: string, user: StatsigUser, options: EvaluationOptions): Layer;
getLayer(
name: string,
user: StatsigUser,
options?: LayerEvaluationOptions,
): Layer;
logEvent(event: StatsigEvent, user: StatsigUser): void;
}

Expand All @@ -48,11 +57,20 @@ export interface PrecomputedEvaluationsInterface
getCurrentUser(): StatsigUser;
updateUserSync(user: StatsigUser): void;
updateUserAsync(user: StatsigUser): Promise<void>;
checkGate(name: string, options: EvaluationOptions): boolean;
getFeatureGate(name: string, options: EvaluationOptions): FeatureGate;
getDynamicConfig(name: string, options: EvaluationOptions): DynamicConfig;
getExperiment(name: string, options: EvaluationOptions): Experiment;
getLayer(name: string, options: EvaluationOptions): Layer;
checkGate(name: string, options?: FeatureGateEvaluationOptions): boolean;
getFeatureGate(
name: string,
options?: FeatureGateEvaluationOptions,
): FeatureGate;
getDynamicConfig(
name: string,
options?: DynamicConfigEvaluationOptions,
): DynamicConfig;
getExperiment(
name: string,
options?: ExperimentEvaluationOptions,
): Experiment;
getLayer(name: string, options?: LayerEvaluationOptions): Layer;
logEvent(event: StatsigEvent): void;
}

Expand Down
30 changes: 30 additions & 0 deletions packages/client-core/src/EvaluationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type EvaluationOptionsCommon = {
/**
* Prevents an exposure log being created for this check.
*
* default: `false`
*/
disableExposureLog?: boolean;
};

export type FeatureGateEvaluationOptions = EvaluationOptionsCommon & {
// Feature Gate specific options
};

export type DynamicConfigEvaluationOptions = EvaluationOptionsCommon & {
// Dynamic Config specific options
};

export type ExperimentEvaluationOptions = EvaluationOptionsCommon & {
/**
* Provide a map of values to be used across checks
*
* @requires {@link @statsig/js-user-persisted-storage}
* @see {@link https://docs.statsig.com/client/concepts/persistent_assignment#example-usage}
*/
userPersistedValues?: unknown;
};

export type LayerEvaluationOptions = EvaluationOptionsCommon & {
// Layer specific options
};
32 changes: 19 additions & 13 deletions packages/client-core/src/EvaluationTypes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Flatten } from './UtitlityTypes';

type EvaluationBase<T> = {
id_type: string;
name: string;
Expand All @@ -14,22 +16,26 @@ export type SecondaryExposure = {

export type GateEvaluation = EvaluationBase<boolean>;

export type ExperimentEvaluation = EvaluationBase<Record<string, unknown>> & {
group_name?: string;
group: string;
id_type: string;
is_device_based: boolean;
is_experiment_active?: boolean;
is_user_in_experiment?: boolean;
};
export type ExperimentEvaluation = Flatten<
EvaluationBase<Record<string, unknown>> & {
group_name?: string;
group: string;
id_type: string;
is_device_based: boolean;
is_experiment_active?: boolean;
is_user_in_experiment?: boolean;
}
>;

export type DynamicConfigEvaluation = ExperimentEvaluation;

export type LayerEvaluation = Omit<ExperimentEvaluation, 'id_type'> & {
allocated_experiment_name: string;
explicit_parameters: string[];
undelegated_secondary_exposures?: SecondaryExposure[];
};
export type LayerEvaluation = Flatten<
Omit<ExperimentEvaluation, 'id_type'> & {
allocated_experiment_name: string;
explicit_parameters: string[];
undelegated_secondary_exposures?: SecondaryExposure[];
}
>;

export type AnyEvaluation =
| GateEvaluation
Expand Down
22 changes: 19 additions & 3 deletions packages/client-core/src/OverrideAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import {
DynamicConfigEvaluationOptions,
ExperimentEvaluationOptions,
FeatureGateEvaluationOptions,
LayerEvaluationOptions,
} from './EvaluationOptions';
import { DynamicConfig, Experiment, FeatureGate, Layer } from './StatsigTypes';
import { StatsigUser } from './StatsigUser';

export type OverrideAdapter = {
getGateOverride?(current: FeatureGate, user: StatsigUser): FeatureGate | null;
getGateOverride?(
current: FeatureGate,
user: StatsigUser,
options?: FeatureGateEvaluationOptions,
): FeatureGate | null;
getDynamicConfigOverride?(
current: DynamicConfig,
user: StatsigUser,
options?: DynamicConfigEvaluationOptions,
): DynamicConfig | null;
getExperimentOverride?(
current: Experiment,
user: StatsigUser,
options?: ExperimentEvaluationOptions,
): Experiment | null;
getLayerOverride?(current: Layer, user: StatsigUser): Layer | null;
getLayerOverride?(
current: Layer,
user: StatsigUser,
options?: LayerEvaluationOptions,
): Layer | null;
};

export class CombinationOverrideProvider implements OverrideAdapter {
export class CombinationOverrideAdapter implements OverrideAdapter {
constructor(public readonly providers: OverrideAdapter[]) {}

getGateOverride(current: FeatureGate, user: StatsigUser): FeatureGate | null {
Expand Down
13 changes: 3 additions & 10 deletions packages/client-core/src/StatsigClientBase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import './$_StatsigGlobal';
import { ErrorBoundary } from './ErrorBoundary';
import { EvaluationOptionsCommon } from './EvaluationOptions';
import { EventLogger } from './EventLogger';
import { Log, LogLevel } from './Log';
import { NetworkCore } from './NetworkCore';
Expand All @@ -24,14 +25,6 @@ import {
} from './StatsigOptionsCommon';
import { Storage } from './StorageProvider';

export type EvaluationOptions = {
disableExposureLog?: boolean;
};

export const DEFAULT_EVAL_OPTIONS: EvaluationOptions = {
disableExposureLog: false,
};

export type StatsigClientEmitEventFunc = (data: StatsigClientEventData) => void;

export abstract class StatsigClientBase<
Expand Down Expand Up @@ -131,10 +124,10 @@ export abstract class StatsigClientBase<

protected _enqueueExposure(
name: string,
options: EvaluationOptions,
exposure: StatsigEventInternal,
options?: EvaluationOptionsCommon,
): void {
if (options.disableExposureLog === true) {
if (options?.disableExposureLog === true) {
this._logger.incrementNonExposureCount(name);
return;
}
Expand Down
15 changes: 3 additions & 12 deletions packages/client-core/src/StatsigEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ export type StatsigEventInternal = Omit<StatsigEvent, 'metadata'> & {
secondaryExposures?: SecondaryExposure[];
};

type LayerEvaluation = {
explicit_parameters: string[] | null;
undelegated_secondary_exposures?: SecondaryExposure[];
secondary_exposures: SecondaryExposure[];
allocated_experiment_name: string | null;
};

const CONFIG_EXPOSURE_NAME = 'statsig::config_exposure';
const GATE_EXPOSURE_NAME = 'statsig::gate_exposure';
const LAYER_EXPOSURE_NAME = 'statsig::layer_exposure';
Expand Down Expand Up @@ -50,7 +43,6 @@ export function isExposureEvent({ eventName }: StatsigEventInternal): boolean {
export function createGateExposure(
user: StatsigUser,
gate: FeatureGate,
secondaryExposures: SecondaryExposure[] | undefined,
): StatsigEventInternal {
return createExposure(
GATE_EXPOSURE_NAME,
Expand All @@ -61,14 +53,13 @@ export function createGateExposure(
gateValue: String(gate.value),
ruleID: gate.ruleID,
},
secondaryExposures ?? [],
gate.__evaluation?.secondary_exposures ?? [],
);
}

export function createConfigExposure(
user: StatsigUser,
config: DynamicConfig,
secondaryExposures: SecondaryExposure[] | undefined,
): StatsigEventInternal {
return createExposure(
CONFIG_EXPOSURE_NAME,
Expand All @@ -78,16 +69,16 @@ export function createConfigExposure(
config: config.name,
ruleID: config.ruleID,
},
secondaryExposures ?? [],
config.__evaluation?.secondary_exposures ?? [],
);
}

export function createLayerParameterExposure(
user: StatsigUser,
layer: Layer,
parameterName: string,
evaluation: LayerEvaluation | null,
): StatsigEventInternal {
const evaluation = layer.__evaluation;
const isExplicit =
evaluation?.explicit_parameters?.includes(parameterName) === true;
let allocatedExperiment = '';
Expand Down
8 changes: 1 addition & 7 deletions packages/client-core/src/StatsigTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ import {
GateEvaluation,
LayerEvaluation,
} from './EvaluationTypes';
import { Flatten } from './UtitlityTypes';

const DEFAULT_RULE = 'default';

export type Flatten<T> = {
[K in keyof T]: T[K];

// Intentional: This is a utility type
// eslint-disable-next-line @typescript-eslint/ban-types
} & {};

type CommonFields = {
readonly name: string;
readonly ruleID: string;
Expand Down
6 changes: 6 additions & 0 deletions packages/client-core/src/UtitlityTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type Flatten<T> = {
[K in keyof T]: T[K];

// Intentional: This is a utility type
// eslint-disable-next-line @typescript-eslint/ban-types
} & {};
2 changes: 2 additions & 0 deletions packages/client-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './$_StatsigGlobal';
export * from './ClientInterfaces';
export * from './DataAdapterCore';
export * from './ErrorBoundary';
export * from './EvaluationOptions';
export * from './EvaluationTypes';
export * from './Hashing';
export * from './Log';
Expand All @@ -26,6 +27,7 @@ export * from './StatsigUser';
export * from './StorageProvider';
export * from './TypedJsonParse';
export * from './UrlOverrides';
export * from './UtitlityTypes';
export * from './UUID';
export * from './VisibilityChangeObserver';

Expand Down
8 changes: 0 additions & 8 deletions packages/client-extensions/README.md

This file was deleted.

12 changes: 0 additions & 12 deletions packages/client-extensions/package.json

This file was deleted.

34 changes: 0 additions & 34 deletions packages/client-extensions/project.json

This file was deleted.

Loading

0 comments on commit 63dafc3

Please sign in to comment.