@statsig/on-device-eval-core 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ ISC License (ISC)
2
+ Copyright (c) 2021, Statsig, Inc.
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any purpose
5
+ with or without fee is hereby granted, provided that the above copyright notice
6
+ and this permission notice appear in all copies.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+ THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ Statsig helps you move faster with feature gates (feature flags), and/or dynamic configs. It also allows you to run A/B/n tests to validate your new features and understand their impact on your KPIs. If you're new to Statsig, check out our product and create an account at [statsig.com](https://www.statsig.com/?ref=gh_jsm).
2
+
3
+ <h1 align="center">
4
+ <a href="https://statsig.com/?ref=gh_jsm">
5
+ <img src="https://github.com/statsig-io/js-client-monorepo/assets/95646168/ae5499ed-20ff-4584-bf21-8857f800d485" />
6
+ </a>
7
+ <div />
8
+ <a href="https://statsig.com/?ref=gh_jsm">Statsig</a>
9
+ </h1>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/statsig-io/js-client-monorepo/blob/main/LICENSE">
13
+ <img src="https://img.shields.io/badge/license-ISC-blue.svg?colorA=1b2528&colorB=ccfbc7&style=for-the-badge">
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@statsig/js-client">
16
+ <img src="https://img.shields.io/npm/v/@statsig/js-client.svg?colorA=1b2528&colorB=b2d3ff&style=for-the-badge">
17
+ </a>
18
+ <a href="https://statsig.com/community?ref=gh_jsm">
19
+ <img src="https://img.shields.io/badge/slack-statsig-brightgreen.svg?logo=slack&colorA=1b2528&colorB=FFF8BA&style=for-the-badge">
20
+ </a>
21
+ </p>
22
+
23
+ ## Getting Started
24
+
25
+ Read through the [Documentation](https://docs.statsig.com/client/javascript-sdk?ref=gh_jsm) or check out the [Samples](samples/).
26
+
27
+ ## Packages
28
+
29
+ Clients
30
+
31
+ - Precomputed Evaluations (Recommended) [[npm](https://www.npmjs.com/package/@statsig/js-client)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/js-client)]
32
+ - On Device Evaluations [[npm](https://www.npmjs.com/package/@statsig/js-on-device-eval-client)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/js-on-device-eval-client)]
33
+
34
+ Product Bundles
35
+
36
+ - Session Replay [[npm](https://www.npmjs.com/package/@statsig/session-replay)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/session-replay)]
37
+ - Web Analytics [[npm](https://www.npmjs.com/package/@statsig/web-analytics)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/web-analytics)]
38
+
39
+ Framework Specific Bindings
40
+
41
+ - React [[npm](https://www.npmjs.com/package/@statsig/react-bindings)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/react-bindings)]
42
+
43
+ - React Native [[npm](https://www.npmjs.com/package/@statsig/react-native-bindings)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/react-native-bindings)]
44
+
45
+ - React Native (Expo) [[npm](https://www.npmjs.com/package/@statsig/expo-bindings)] [[source](https://github.com/statsig-io/js-client-monorepo/tree/main/packages/expo-bindings)]
46
+
47
+ ## Community
48
+
49
+ If you need any assitance or just have a question, feel free to reach out to us on [Slack](https://statsig.com/community?ref=gh_jsm).
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@statsig/on-device-eval-core",
3
+ "version": "3.12.0",
4
+ "dependencies": {
5
+ "@statsig/client-core": "3.12.0",
6
+ "@statsig/sha256": "3.12.0"
7
+ },
8
+ "type": "commonjs",
9
+ "main": "./src/index.js",
10
+ "typings": "./src/index.d.ts"
11
+ }
@@ -0,0 +1,10 @@
1
+ declare const _default: {
2
+ compareNumbers(left: unknown, right: unknown, operator: string): boolean;
3
+ compareVersions(left: unknown, right: unknown, operator: string): boolean;
4
+ compareStringInArray(value: unknown, array: unknown, operator: string): boolean;
5
+ compareStringWithRegEx(value: unknown, target: unknown): boolean;
6
+ compareTime(left: unknown, right: unknown, operator: string): boolean;
7
+ arrayHasValue(value: unknown[], target: string[]): boolean;
8
+ arrayHasAllValues(value: unknown[], target: string[]): boolean;
9
+ };
10
+ export default _default;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ compareNumbers(left, right, operator) {
5
+ if (left == null || right == null) {
6
+ return false;
7
+ }
8
+ const numA = Number(left);
9
+ const numB = Number(right);
10
+ if (isNaN(numA) || isNaN(numB)) {
11
+ return false;
12
+ }
13
+ switch (operator) {
14
+ case 'gt':
15
+ return left > right;
16
+ case 'gte':
17
+ return left >= right;
18
+ case 'lt':
19
+ return left < right;
20
+ case 'lte':
21
+ return left <= right;
22
+ default:
23
+ return false;
24
+ }
25
+ },
26
+ compareVersions(left, right, operator) {
27
+ if (left == null || right == null) {
28
+ return false;
29
+ }
30
+ let leftStr = String(left);
31
+ let rightStr = String(right);
32
+ const removeSuffix = (str) => {
33
+ const index = str.indexOf('-');
34
+ return index !== -1 ? str.substring(0, index) : str;
35
+ };
36
+ leftStr = removeSuffix(leftStr);
37
+ rightStr = removeSuffix(rightStr);
38
+ const comparison = (leftStr, rightStr) => {
39
+ const leftParts = leftStr.split('.').map((part) => parseInt(part));
40
+ const rightParts = rightStr.split('.').map((part) => parseInt(part));
41
+ let i = 0;
42
+ while (i < Math.max(leftParts.length, rightParts.length)) {
43
+ const leftCount = i < leftParts.length ? leftParts[i] : 0;
44
+ const rightCount = i < rightParts.length ? rightParts[i] : 0;
45
+ if (leftCount < rightCount) {
46
+ return -1;
47
+ }
48
+ if (leftCount > rightCount) {
49
+ return 1;
50
+ }
51
+ i++;
52
+ }
53
+ return 0;
54
+ };
55
+ const result = comparison(leftStr, rightStr);
56
+ switch (operator) {
57
+ case 'version_gt':
58
+ return result > 0;
59
+ case 'version_gte':
60
+ return result >= 0;
61
+ case 'version_lt':
62
+ return result < 0;
63
+ case 'version_lte':
64
+ return result <= 0;
65
+ case 'version_eq':
66
+ return result === 0;
67
+ case 'version_neq':
68
+ return result !== 0;
69
+ default:
70
+ return false;
71
+ }
72
+ },
73
+ compareStringInArray(value, array, operator) {
74
+ if (!Array.isArray(array)) {
75
+ return false;
76
+ }
77
+ const ignoreCase = operator !== 'any_case_sensitive' && operator !== 'none_case_sensitive';
78
+ const result = array.findIndex((current) => {
79
+ const valueString = String(value);
80
+ const currentString = String(current);
81
+ const left = ignoreCase ? valueString.toLowerCase() : valueString;
82
+ const right = ignoreCase ? currentString.toLowerCase() : currentString;
83
+ switch (operator) {
84
+ case 'any':
85
+ case 'none':
86
+ case 'any_case_sensitive':
87
+ case 'none_case_sensitive':
88
+ return left === right;
89
+ case 'str_starts_with_any':
90
+ return left.startsWith(right);
91
+ case 'str_ends_with_any':
92
+ return left.endsWith(right);
93
+ case 'str_contains_any':
94
+ case 'str_contains_none':
95
+ return left.includes(right);
96
+ default:
97
+ return false;
98
+ }
99
+ }) !== -1;
100
+ switch (operator) {
101
+ case 'none':
102
+ case 'none_case_sensitive':
103
+ case 'str_contains_none':
104
+ return !result;
105
+ default:
106
+ return result;
107
+ }
108
+ },
109
+ compareStringWithRegEx(value, target) {
110
+ try {
111
+ const valueString = String(value);
112
+ if (valueString.length < 1000) {
113
+ return new RegExp(String(target)).test(valueString);
114
+ }
115
+ }
116
+ catch (e) {
117
+ // noop
118
+ }
119
+ return false;
120
+ },
121
+ compareTime(left, right, operator) {
122
+ if (left == null || right == null) {
123
+ return false;
124
+ }
125
+ try {
126
+ // Try to parse into date as a string first, if not, try unixtime
127
+ let dateLeft = new Date(String(left));
128
+ if (isNaN(dateLeft.getTime())) {
129
+ dateLeft = new Date(Number(left));
130
+ }
131
+ let dateRight = new Date(String(right));
132
+ if (isNaN(dateRight.getTime())) {
133
+ dateRight = new Date(Number(right));
134
+ }
135
+ const timeLeft = dateLeft.getTime();
136
+ const timeRight = dateRight.getTime();
137
+ if (isNaN(timeLeft) || isNaN(timeRight)) {
138
+ return false;
139
+ }
140
+ switch (operator) {
141
+ case 'before':
142
+ return timeLeft < timeRight;
143
+ case 'after':
144
+ return timeLeft > timeRight;
145
+ case 'on':
146
+ return _startOfDay(dateLeft) === _startOfDay(dateRight);
147
+ default:
148
+ return false;
149
+ }
150
+ }
151
+ catch (e) {
152
+ // malformatted input, returning false
153
+ return false;
154
+ }
155
+ },
156
+ arrayHasValue(value, target) {
157
+ const valueSet = new Set(value);
158
+ for (let i = 0; i < target.length; i++) {
159
+ if (valueSet.has(target[i]) ||
160
+ valueSet.has(parseFloat(target[i]))) {
161
+ return true;
162
+ }
163
+ }
164
+ return false;
165
+ },
166
+ arrayHasAllValues(value, target) {
167
+ const valueSet = new Set(value);
168
+ for (let i = 0; i < target.length; i++) {
169
+ if (!valueSet.has(target[i]) &&
170
+ !valueSet.has(parseFloat(target[i]))) {
171
+ return false;
172
+ }
173
+ }
174
+ return true;
175
+ },
176
+ };
177
+ function _startOfDay(date) {
178
+ date.setUTCHours(0, 0, 0, 0);
179
+ return date.getTime();
180
+ }
@@ -0,0 +1,17 @@
1
+ import { DynamicConfigEvaluation, GateEvaluation, LayerEvaluation, SecondaryExposure, Spec } from '@statsig/client-core';
2
+ export type EvaluationResult = {
3
+ readonly unsupported: boolean;
4
+ readonly bool_value: boolean;
5
+ readonly rule_id: string;
6
+ readonly secondary_exposures: SecondaryExposure[];
7
+ readonly json_value: Record<string, unknown>;
8
+ readonly explicit_parameters: string[] | null;
9
+ readonly allocated_experiment_name: string | null;
10
+ readonly undelegated_secondary_exposures: SecondaryExposure[] | undefined;
11
+ readonly is_experiment_group: boolean;
12
+ readonly group_name: string | null;
13
+ };
14
+ export declare function makeEvalResult(overrides: Partial<EvaluationResult>): EvaluationResult;
15
+ export declare function resultToGateEval(spec: Spec, result: EvaluationResult): GateEvaluation;
16
+ export declare function resultToConfigEval(spec: Spec, result: EvaluationResult): DynamicConfigEvaluation;
17
+ export declare function resultToLayerEval(layerSpec: Spec, experimentSpec: Spec | null, result: EvaluationResult): LayerEvaluation;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resultToLayerEval = exports.resultToConfigEval = exports.resultToGateEval = exports.makeEvalResult = void 0;
4
+ function makeEvalResult(overrides) {
5
+ const base = {
6
+ unsupported: false,
7
+ bool_value: false,
8
+ rule_id: '',
9
+ secondary_exposures: [],
10
+ json_value: {},
11
+ explicit_parameters: null,
12
+ allocated_experiment_name: null,
13
+ is_experiment_group: false,
14
+ group_name: null,
15
+ undelegated_secondary_exposures: undefined,
16
+ };
17
+ return Object.assign(Object.assign({}, base), overrides);
18
+ }
19
+ exports.makeEvalResult = makeEvalResult;
20
+ function resultToGateEval(spec, result) {
21
+ var _a;
22
+ return {
23
+ name: spec.name,
24
+ id_type: spec.idType,
25
+ rule_id: result.rule_id,
26
+ value: result.bool_value,
27
+ secondary_exposures: result.secondary_exposures,
28
+ version: (_a = spec.version) === null || _a === void 0 ? void 0 : _a.toString(),
29
+ };
30
+ }
31
+ exports.resultToGateEval = resultToGateEval;
32
+ function resultToConfigEval(spec, result) {
33
+ var _a, _b, _c;
34
+ return {
35
+ name: spec.name,
36
+ id_type: spec.idType,
37
+ rule_id: result.rule_id,
38
+ value: result.json_value,
39
+ secondary_exposures: result.secondary_exposures,
40
+ group: (_a = result.group_name) !== null && _a !== void 0 ? _a : '',
41
+ group_name: (_b = result.group_name) !== null && _b !== void 0 ? _b : undefined,
42
+ is_device_based: false,
43
+ is_experiment_active: spec.isActive,
44
+ is_user_in_experiment: result.is_experiment_group,
45
+ version: (_c = spec.version) === null || _c === void 0 ? void 0 : _c.toString(),
46
+ passed: result.bool_value,
47
+ };
48
+ }
49
+ exports.resultToConfigEval = resultToConfigEval;
50
+ function resultToLayerEval(layerSpec, experimentSpec, result) {
51
+ var _a, _b, _c, _d, _e;
52
+ return {
53
+ name: layerSpec.name,
54
+ rule_id: result.rule_id,
55
+ value: result.json_value,
56
+ secondary_exposures: result.secondary_exposures,
57
+ undelegated_secondary_exposures: result.undelegated_secondary_exposures,
58
+ allocated_experiment_name: (_a = result.allocated_experiment_name) !== null && _a !== void 0 ? _a : '',
59
+ explicit_parameters: (_b = result.explicit_parameters) !== null && _b !== void 0 ? _b : [],
60
+ group: (_c = result.group_name) !== null && _c !== void 0 ? _c : '',
61
+ group_name: (_d = result.group_name) !== null && _d !== void 0 ? _d : undefined,
62
+ is_device_based: false,
63
+ is_experiment_active: experimentSpec === null || experimentSpec === void 0 ? void 0 : experimentSpec.isActive,
64
+ is_user_in_experiment: result.is_experiment_group,
65
+ version: (_e = layerSpec.version) === null || _e === void 0 ? void 0 : _e.toString(),
66
+ };
67
+ }
68
+ exports.resultToLayerEval = resultToLayerEval;
@@ -0,0 +1,22 @@
1
+ import { DynamicConfigEvaluation, EvaluationDetails, GateEvaluation, LayerEvaluation, StatsigUserInternal } from '@statsig/client-core';
2
+ import { SpecStore } from './SpecStore';
3
+ type DetailedEvaluation<T> = {
4
+ evaluation: T | null;
5
+ details: EvaluationDetails;
6
+ };
7
+ export declare class Evaluator {
8
+ private _store;
9
+ constructor(_store: SpecStore);
10
+ evaluateGate(name: string, user: StatsigUserInternal): DetailedEvaluation<GateEvaluation>;
11
+ evaluateConfig(name: string, user: StatsigUserInternal): DetailedEvaluation<DynamicConfigEvaluation>;
12
+ evaluateLayer(name: string, user: StatsigUserInternal): DetailedEvaluation<LayerEvaluation>;
13
+ private _getSpecAndDetails;
14
+ private _getEvaluationDetails;
15
+ private _evaluateSpec;
16
+ private _evaluateRule;
17
+ private _evaluateCondition;
18
+ private _evaluateDelegate;
19
+ private _evaluateNestedGate;
20
+ private _evaluateMultiNestedGates;
21
+ }
22
+ export {};
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Evaluator = void 0;
4
+ const sha256_1 = require("@statsig/sha256");
5
+ const EvaluationComparison_1 = require("./EvaluationComparison");
6
+ const EvaluationResult_1 = require("./EvaluationResult");
7
+ const CONDITION_SEGMENT_COUNT = 10 * 1000;
8
+ const USER_BUCKET_COUNT = 1000;
9
+ class Evaluator {
10
+ constructor(_store) {
11
+ this._store = _store;
12
+ }
13
+ evaluateGate(name, user) {
14
+ const { spec, details } = this._getSpecAndDetails('gate', name);
15
+ if (!spec) {
16
+ return { evaluation: null, details };
17
+ }
18
+ const evaluation = (0, EvaluationResult_1.resultToGateEval)(spec, this._evaluateSpec(spec, user));
19
+ return { evaluation, details };
20
+ }
21
+ evaluateConfig(name, user) {
22
+ const { spec, details } = this._getSpecAndDetails('config', name);
23
+ if (!spec) {
24
+ return { evaluation: null, details };
25
+ }
26
+ const evaluation = (0, EvaluationResult_1.resultToConfigEval)(spec, this._evaluateSpec(spec, user));
27
+ return { evaluation, details };
28
+ }
29
+ evaluateLayer(name, user) {
30
+ var _a;
31
+ const { spec, details } = this._getSpecAndDetails('layer', name);
32
+ if (!spec) {
33
+ return { evaluation: null, details };
34
+ }
35
+ const result = this._evaluateSpec(spec, user);
36
+ const experimentName = (_a = result === null || result === void 0 ? void 0 : result.allocated_experiment_name) !== null && _a !== void 0 ? _a : '';
37
+ const experimentSpec = this._store.getSpecAndSourceInfo('config', experimentName).spec;
38
+ const evaluation = (0, EvaluationResult_1.resultToLayerEval)(spec, experimentSpec, result);
39
+ return { evaluation, details };
40
+ }
41
+ _getSpecAndDetails(kind, name) {
42
+ const specAndSourceInfo = this._store.getSpecAndSourceInfo(kind, name);
43
+ const details = this._getEvaluationDetails(specAndSourceInfo);
44
+ return { details, spec: specAndSourceInfo.spec };
45
+ }
46
+ _getEvaluationDetails(info) {
47
+ const { source, spec, lcut, receivedAt } = info;
48
+ if (source === 'Uninitialized' || source === 'NoValues') {
49
+ return { reason: source };
50
+ }
51
+ const subreason = spec == null ? 'Unrecognized' : 'Recognized';
52
+ const reason = `${source}:${subreason}`;
53
+ return { reason, lcut, receivedAt };
54
+ }
55
+ _evaluateSpec(spec, user) {
56
+ const defaultValue = _isRecord(spec.defaultValue)
57
+ ? spec.defaultValue
58
+ : undefined;
59
+ if (!spec.enabled) {
60
+ return (0, EvaluationResult_1.makeEvalResult)({
61
+ json_value: defaultValue,
62
+ rule_id: 'disabled',
63
+ });
64
+ }
65
+ const exposures = [];
66
+ for (const rule of spec.rules) {
67
+ const result = this._evaluateRule(rule, user);
68
+ if (result.unsupported) {
69
+ return result;
70
+ }
71
+ exposures.push(...result.secondary_exposures);
72
+ if (!result.bool_value) {
73
+ continue;
74
+ }
75
+ const delegateResult = this._evaluateDelegate(rule.configDelegate, user, exposures);
76
+ if (delegateResult) {
77
+ return delegateResult;
78
+ }
79
+ const pass = _evalPassPercent(rule, user, spec);
80
+ return (0, EvaluationResult_1.makeEvalResult)({
81
+ rule_id: result.rule_id,
82
+ bool_value: pass,
83
+ json_value: pass ? result.json_value : defaultValue,
84
+ secondary_exposures: exposures,
85
+ undelegated_secondary_exposures: exposures,
86
+ is_experiment_group: result.is_experiment_group,
87
+ group_name: result.group_name,
88
+ });
89
+ }
90
+ return (0, EvaluationResult_1.makeEvalResult)({
91
+ json_value: defaultValue,
92
+ secondary_exposures: exposures,
93
+ undelegated_secondary_exposures: exposures,
94
+ rule_id: 'default',
95
+ });
96
+ }
97
+ _evaluateRule(rule, user) {
98
+ const exposures = [];
99
+ let pass = true;
100
+ for (const condition of rule.conditions) {
101
+ const result = this._evaluateCondition(condition, user);
102
+ if (result.unsupported) {
103
+ return result;
104
+ }
105
+ exposures.push(...result.secondary_exposures);
106
+ if (!result.bool_value) {
107
+ pass = false;
108
+ }
109
+ }
110
+ return (0, EvaluationResult_1.makeEvalResult)({
111
+ rule_id: rule.id,
112
+ bool_value: pass,
113
+ json_value: _isRecord(rule.returnValue) ? rule.returnValue : undefined,
114
+ secondary_exposures: exposures,
115
+ is_experiment_group: rule.isExperimentGroup === true,
116
+ group_name: rule.groupName,
117
+ });
118
+ }
119
+ _evaluateCondition(condition, user) {
120
+ var _a, _b, _c;
121
+ let value = null;
122
+ let pass = false;
123
+ const field = condition.field;
124
+ const target = condition.targetValue;
125
+ const idType = condition.idType;
126
+ const type = condition.type;
127
+ switch (type) {
128
+ case 'public':
129
+ return (0, EvaluationResult_1.makeEvalResult)({ bool_value: true });
130
+ case 'pass_gate':
131
+ case 'fail_gate': {
132
+ const name = String(target);
133
+ const result = this._evaluateNestedGate(name, user);
134
+ return (0, EvaluationResult_1.makeEvalResult)({
135
+ bool_value: type === 'fail_gate' ? !result.bool_value : result.bool_value,
136
+ secondary_exposures: result.secondary_exposures,
137
+ });
138
+ }
139
+ case 'multi_pass_gate':
140
+ case 'multi_fail_gate':
141
+ return this._evaluateMultiNestedGates(target, type, user);
142
+ case 'user_field':
143
+ case 'ip_based':
144
+ case 'ua_based':
145
+ value = _getFromUser(user, field);
146
+ break;
147
+ case 'environment_field':
148
+ value = _getFromEnvironment(user, field);
149
+ break;
150
+ case 'current_time':
151
+ value = Date.now();
152
+ break;
153
+ case 'user_bucket': {
154
+ const salt = String((_b = (_a = condition.additionalValues) === null || _a === void 0 ? void 0 : _a['salt']) !== null && _b !== void 0 ? _b : '');
155
+ const unitID = (_c = _getUnitIDFromUser(user, idType)) !== null && _c !== void 0 ? _c : '';
156
+ const userHash = _computeUserHash(salt + '.' + unitID);
157
+ value = Number(userHash % BigInt(USER_BUCKET_COUNT));
158
+ break;
159
+ }
160
+ case 'unit_id':
161
+ value = _getUnitIDFromUser(user, idType);
162
+ break;
163
+ default:
164
+ return (0, EvaluationResult_1.makeEvalResult)({ unsupported: true });
165
+ }
166
+ const operator = condition.operator;
167
+ switch (operator) {
168
+ case 'gt':
169
+ case 'gte':
170
+ case 'lt':
171
+ case 'lte':
172
+ pass = EvaluationComparison_1.default.compareNumbers(value, target, operator);
173
+ break;
174
+ case 'version_gt':
175
+ case 'version_gte':
176
+ case 'version_lt':
177
+ case 'version_lte':
178
+ case 'version_eq':
179
+ case 'version_neq':
180
+ pass = EvaluationComparison_1.default.compareVersions(value, target, operator);
181
+ break;
182
+ case 'any':
183
+ case 'none':
184
+ case 'str_starts_with_any':
185
+ case 'str_ends_with_any':
186
+ case 'str_contains_any':
187
+ case 'str_contains_none':
188
+ case 'any_case_sensitive':
189
+ case 'none_case_sensitive':
190
+ pass = EvaluationComparison_1.default.compareStringInArray(value, target, operator);
191
+ break;
192
+ case 'str_matches':
193
+ pass = EvaluationComparison_1.default.compareStringWithRegEx(value, target);
194
+ break;
195
+ case 'before':
196
+ case 'after':
197
+ case 'on':
198
+ pass = EvaluationComparison_1.default.compareTime(value, target, operator);
199
+ break;
200
+ case 'eq':
201
+ // eslint-disable-next-line eqeqeq
202
+ pass = value == target;
203
+ break;
204
+ case 'neq':
205
+ // eslint-disable-next-line eqeqeq
206
+ pass = value != target;
207
+ break;
208
+ case 'in_segment_list':
209
+ case 'not_in_segment_list':
210
+ return (0, EvaluationResult_1.makeEvalResult)({ unsupported: true });
211
+ case 'array_contains_any':
212
+ case 'array_contains_none': {
213
+ if (!Array.isArray(target)) {
214
+ pass = false;
215
+ break;
216
+ }
217
+ if (!Array.isArray(value)) {
218
+ pass = false;
219
+ break;
220
+ }
221
+ const res = EvaluationComparison_1.default.arrayHasValue(value, target);
222
+ pass = operator === 'array_contains_any' ? res : !res;
223
+ break;
224
+ }
225
+ case 'array_contains_all':
226
+ case 'not_array_contains_all': {
227
+ if (!Array.isArray(target)) {
228
+ pass = false;
229
+ break;
230
+ }
231
+ if (!Array.isArray(value)) {
232
+ pass = false;
233
+ break;
234
+ }
235
+ const res = EvaluationComparison_1.default.arrayHasAllValues(value, target);
236
+ pass = operator === 'array_contains_all' ? res : !res;
237
+ break;
238
+ }
239
+ }
240
+ return (0, EvaluationResult_1.makeEvalResult)({ bool_value: pass });
241
+ }
242
+ _evaluateDelegate(configDelegate, user, exposures) {
243
+ if (!configDelegate) {
244
+ return null;
245
+ }
246
+ const { spec } = this._store.getSpecAndSourceInfo('config', configDelegate);
247
+ if (!spec) {
248
+ return null;
249
+ }
250
+ const result = this._evaluateSpec(spec, user);
251
+ return (0, EvaluationResult_1.makeEvalResult)(Object.assign(Object.assign({}, result), { allocated_experiment_name: configDelegate, explicit_parameters: spec.explicitParameters, secondary_exposures: exposures.concat(result.secondary_exposures), undelegated_secondary_exposures: exposures }));
252
+ }
253
+ _evaluateNestedGate(name, user) {
254
+ const exposures = [];
255
+ let pass = false;
256
+ const { spec } = this._store.getSpecAndSourceInfo('gate', name);
257
+ if (spec) {
258
+ const result = this._evaluateSpec(spec, user);
259
+ if (result.unsupported) {
260
+ return result;
261
+ }
262
+ pass = result.bool_value;
263
+ exposures.push(...result.secondary_exposures);
264
+ exposures.push({
265
+ gate: name,
266
+ gateValue: String(pass),
267
+ ruleID: result.rule_id,
268
+ });
269
+ }
270
+ return (0, EvaluationResult_1.makeEvalResult)({
271
+ bool_value: pass,
272
+ secondary_exposures: exposures,
273
+ });
274
+ }
275
+ _evaluateMultiNestedGates(gates, type, user) {
276
+ if (!Array.isArray(gates)) {
277
+ return (0, EvaluationResult_1.makeEvalResult)({ unsupported: true });
278
+ }
279
+ const isMultiPassType = type === 'multi_pass_gate';
280
+ const exposures = [];
281
+ let pass = false;
282
+ for (const name of gates) {
283
+ if (typeof name !== 'string') {
284
+ return (0, EvaluationResult_1.makeEvalResult)({ unsupported: true });
285
+ }
286
+ const result = this._evaluateNestedGate(name, user);
287
+ if (result.unsupported) {
288
+ return result;
289
+ }
290
+ exposures.push(...result.secondary_exposures);
291
+ if (isMultiPassType
292
+ ? result.bool_value === true
293
+ : result.bool_value === false) {
294
+ pass = true;
295
+ break;
296
+ }
297
+ }
298
+ return (0, EvaluationResult_1.makeEvalResult)({
299
+ bool_value: pass,
300
+ secondary_exposures: exposures,
301
+ });
302
+ }
303
+ }
304
+ exports.Evaluator = Evaluator;
305
+ function _getUnitIDFromUser(user, idType) {
306
+ var _a, _b, _c;
307
+ if (typeof idType === 'string' && idType.toLowerCase() !== 'userid') {
308
+ return (_b = (_a = user.customIDs) === null || _a === void 0 ? void 0 : _a[idType]) !== null && _b !== void 0 ? _b : (_c = user === null || user === void 0 ? void 0 : user.customIDs) === null || _c === void 0 ? void 0 : _c[idType.toLowerCase()];
309
+ }
310
+ return user.userID;
311
+ }
312
+ function _evalPassPercent(rule, user, config) {
313
+ var _a, _b;
314
+ if (rule.passPercentage === 100) {
315
+ return true;
316
+ }
317
+ if (rule.passPercentage === 0) {
318
+ return false;
319
+ }
320
+ const hash = _computeUserHash(config.salt +
321
+ '.' +
322
+ ((_a = rule.salt) !== null && _a !== void 0 ? _a : rule.id) +
323
+ '.' +
324
+ ((_b = _getUnitIDFromUser(user, rule.idType)) !== null && _b !== void 0 ? _b : ''));
325
+ return (Number(hash % BigInt(CONDITION_SEGMENT_COUNT)) < rule.passPercentage * 100);
326
+ }
327
+ function _computeUserHash(userHash) {
328
+ const sha256 = (0, sha256_1.SHA256)(userHash);
329
+ return sha256.dataView().getBigUint64(0, false);
330
+ }
331
+ function _getFromEnvironment(user, field) {
332
+ if (field == null) {
333
+ return null;
334
+ }
335
+ return _getParameterCaseInsensitive(user.statsigEnvironment, field);
336
+ }
337
+ function _getParameterCaseInsensitive(object, key) {
338
+ if (object == null) {
339
+ return undefined;
340
+ }
341
+ const asLowercase = key.toLowerCase();
342
+ const keyMatch = Object.keys(object).find((k) => k.toLowerCase() === asLowercase);
343
+ if (keyMatch === undefined) {
344
+ return undefined;
345
+ }
346
+ return object[keyMatch];
347
+ }
348
+ function _getFromUser(user, field) {
349
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
350
+ if (field == null || typeof user !== 'object' || user == null) {
351
+ return null;
352
+ }
353
+ const indexableUser = user;
354
+ return ((_h = (_f = (_d = (_b = (_a = indexableUser[field]) !== null && _a !== void 0 ? _a : indexableUser[field.toLowerCase()]) !== null && _b !== void 0 ? _b : (_c = user === null || user === void 0 ? void 0 : user.custom) === null || _c === void 0 ? void 0 : _c[field]) !== null && _d !== void 0 ? _d : (_e = user === null || user === void 0 ? void 0 : user.custom) === null || _e === void 0 ? void 0 : _e[field.toLowerCase()]) !== null && _f !== void 0 ? _f : (_g = user === null || user === void 0 ? void 0 : user.privateAttributes) === null || _g === void 0 ? void 0 : _g[field]) !== null && _h !== void 0 ? _h : (_j = user === null || user === void 0 ? void 0 : user.privateAttributes) === null || _j === void 0 ? void 0 : _j[field.toLowerCase()]);
355
+ }
356
+ function _isRecord(obj) {
357
+ return obj != null && typeof obj === 'object';
358
+ }
@@ -0,0 +1,24 @@
1
+ import { DataAdapterResult, DataSource, DownloadConfigSpecsResponse, Spec } from '@statsig/client-core';
2
+ export type SpecAndSourceInfo = {
3
+ spec: Spec | null;
4
+ source: DataSource;
5
+ lcut: number;
6
+ receivedAt: number;
7
+ };
8
+ export type SpecKind = 'gate' | 'config' | 'layer';
9
+ export declare class SpecStore {
10
+ private _rawValues;
11
+ private _values;
12
+ private _source;
13
+ private _lcut;
14
+ private _receivedAt;
15
+ private _defaultEnvironment;
16
+ getValues(): DownloadConfigSpecsResponse | null;
17
+ getSource(): DataSource;
18
+ getDefaultEnvironment(): string | null;
19
+ setValuesFromDataAdapter(result: DataAdapterResult | null): void;
20
+ reset(): void;
21
+ finalize(): void;
22
+ getSpecAndSourceInfo(kind: SpecKind, name: string): SpecAndSourceInfo;
23
+ private _getSpecs;
24
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecStore = void 0;
4
+ const client_core_1 = require("@statsig/client-core");
5
+ function _parseResponse(values) {
6
+ return (0, client_core_1._typedJsonParse)(values, 'has_updates', 'DownloadConfigSpecsResponse');
7
+ }
8
+ class SpecStore {
9
+ constructor() {
10
+ this._rawValues = null;
11
+ this._values = null;
12
+ this._source = 'Uninitialized';
13
+ this._lcut = 0;
14
+ this._receivedAt = 0;
15
+ this._defaultEnvironment = null;
16
+ }
17
+ getValues() {
18
+ return this._rawValues ? _parseResponse(this._rawValues) : null;
19
+ }
20
+ getSource() {
21
+ return this._source;
22
+ }
23
+ getDefaultEnvironment() {
24
+ return this._defaultEnvironment;
25
+ }
26
+ setValuesFromDataAdapter(result) {
27
+ var _a;
28
+ if (!result) {
29
+ return;
30
+ }
31
+ const values = _parseResponse(result.data);
32
+ if ((values === null || values === void 0 ? void 0 : values.has_updates) !== true) {
33
+ return;
34
+ }
35
+ this._lcut = values.time;
36
+ this._receivedAt = result.receivedAt;
37
+ this._source = result.source;
38
+ this._values = values;
39
+ this._rawValues = result.data;
40
+ this._defaultEnvironment = (_a = values.default_environment) !== null && _a !== void 0 ? _a : null;
41
+ }
42
+ reset() {
43
+ this._values = null;
44
+ this._rawValues = null;
45
+ this._source = 'Loading';
46
+ }
47
+ finalize() {
48
+ if (this._values) {
49
+ return;
50
+ }
51
+ this._source = 'NoValues';
52
+ }
53
+ getSpecAndSourceInfo(kind, name) {
54
+ var _a;
55
+ // todo: use Object instead of Array
56
+ const specs = this._getSpecs(kind);
57
+ return {
58
+ spec: (_a = specs === null || specs === void 0 ? void 0 : specs.find((spec) => spec.name === name)) !== null && _a !== void 0 ? _a : null,
59
+ source: this._source,
60
+ lcut: this._lcut,
61
+ receivedAt: this._receivedAt,
62
+ };
63
+ }
64
+ _getSpecs(kind) {
65
+ var _a, _b, _c;
66
+ switch (kind) {
67
+ case 'gate':
68
+ return (_a = this._values) === null || _a === void 0 ? void 0 : _a.feature_gates;
69
+ case 'config':
70
+ return (_b = this._values) === null || _b === void 0 ? void 0 : _b.dynamic_configs;
71
+ case 'layer':
72
+ return (_c = this._values) === null || _c === void 0 ? void 0 : _c.layer_configs;
73
+ }
74
+ }
75
+ }
76
+ exports.SpecStore = SpecStore;
package/src/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './Evaluator';
2
+ export * from './SpecStore';
package/src/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Evaluator"), exports);
18
+ __exportStar(require("./SpecStore"), exports);