@featurevisor/sdk 0.0.3

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.
@@ -0,0 +1,49 @@
1
+ export function parseJsonConditionsIfStringified(record, key) {
2
+ if (typeof record[key] === "string" && record[key] !== "*") {
3
+ try {
4
+ record[key] = JSON.parse(record[key]);
5
+ }
6
+ catch (e) {
7
+ console.error("Error parsing JSON", e);
8
+ }
9
+ }
10
+ return record;
11
+ }
12
+ var DatafileReader = /** @class */ (function () {
13
+ function DatafileReader(datafileJson) {
14
+ this.schemaVersion = datafileJson.schemaVersion;
15
+ this.revision = datafileJson.revision;
16
+ this.segments = datafileJson.segments;
17
+ this.attributes = datafileJson.attributes;
18
+ this.features = datafileJson.features;
19
+ }
20
+ DatafileReader.prototype.getRevision = function () {
21
+ return this.revision;
22
+ };
23
+ DatafileReader.prototype.getSchemaVersion = function () {
24
+ return this.schemaVersion;
25
+ };
26
+ DatafileReader.prototype.getAllAttributes = function () {
27
+ return this.attributes;
28
+ };
29
+ DatafileReader.prototype.getAttribute = function (attributeKey) {
30
+ return this.attributes.find(function (a) { return a.key === attributeKey; });
31
+ };
32
+ DatafileReader.prototype.getSegment = function (segmentKey) {
33
+ var segment = this.segments.find(function (s) { return s.key === segmentKey; });
34
+ if (!segment) {
35
+ return undefined;
36
+ }
37
+ return parseJsonConditionsIfStringified(segment, "conditions");
38
+ };
39
+ DatafileReader.prototype.getFeature = function (featureKey) {
40
+ var feature = this.features.find(function (s) { return s.key === featureKey; });
41
+ if (!feature) {
42
+ return undefined;
43
+ }
44
+ return feature;
45
+ };
46
+ return DatafileReader;
47
+ }());
48
+ export { DatafileReader };
49
+ //# sourceMappingURL=datafileReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datafileReader.js","sourceRoot":"","sources":["../src/datafileReader.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,gCAAgC,CAAI,MAAS,EAAE,GAAW;IACxE,IAAI,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE;QAC1D,IAAI;YACF,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;SACvC;QAAC,OAAO,CAAC,EAAE;YACV,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;SACxC;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;IAOE,wBAAY,YAA6B;QACvC,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC,aAAa,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;QAC1C,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;IACxC,CAAC;IAED,oCAAW,GAAX;QACE,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,yCAAgB,GAAhB;QACE,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,yCAAgB,GAAhB;QACE,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,qCAAY,GAAZ,UAAa,YAA0B;QACrC,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,GAAG,KAAK,YAAY,EAAtB,CAAsB,CAAC,CAAC;IAC7D,CAAC;IAED,mCAAU,GAAV,UAAW,UAAsB;QAC/B,IAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,GAAG,KAAK,UAAU,EAApB,CAAoB,CAAC,CAAC;QAEhE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,SAAS,CAAC;SAClB;QAED,OAAO,gCAAgC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACjE,CAAC;IAED,mCAAU,GAAV,UAAW,UAAsB;QAC/B,IAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,GAAG,KAAK,UAAU,EAApB,CAAoB,CAAC,CAAC;QAEhE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,SAAS,CAAC;SAClB;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IACH,qBAAC;AAAD,CAAC,AAlDD,IAkDC"}
@@ -0,0 +1,8 @@
1
+ import { Allocation, Attributes, Traffic, Feature, Variation, VariableKey, VariableValue } from "@featurevisor/types";
2
+ import { DatafileReader } from "./datafileReader";
3
+ export declare function getMatchedTraffic(traffic: Traffic[], attributes: Attributes, bucketValue: number, datafileReader: DatafileReader): Traffic | undefined;
4
+ export declare function getMatchedAllocation(matchedTraffic: Traffic, bucketValue: number): Allocation | undefined;
5
+ export declare function getForcedVariation(feature: Feature, attributes: Attributes, datafileReader: DatafileReader): Variation | undefined;
6
+ export declare function getBucketedVariation(feature: Feature, attributes: Attributes, bucketValue: number, datafileReader: DatafileReader): Variation | undefined;
7
+ export declare function getForcedVariableValue(feature: Feature, variableKey: VariableKey, attributes: Attributes, datafileReader: DatafileReader): VariableValue | undefined;
8
+ export declare function getBucketedVariableValue(feature: Feature, variableKey: VariableKey, attributes: Attributes, bucketValue: number, datafileReader: DatafileReader): VariableValue | undefined;
package/lib/feature.js ADDED
@@ -0,0 +1,117 @@
1
+ import { allGroupSegmentsAreMatched } from "./segments";
2
+ import { allConditionsAreMatched } from "./conditions";
3
+ export function getMatchedTraffic(traffic, attributes, bucketValue, datafileReader) {
4
+ return traffic.find(function (traffic) {
5
+ if (bucketValue > traffic.percentage) {
6
+ // out of bucket range
7
+ return false;
8
+ }
9
+ if (!allGroupSegmentsAreMatched(typeof traffic.segments === "string" && traffic.segments !== "*"
10
+ ? JSON.parse(traffic.segments)
11
+ : traffic.segments, attributes, datafileReader)) {
12
+ return false;
13
+ }
14
+ return true;
15
+ });
16
+ }
17
+ // @TODO: make this function better with tests
18
+ export function getMatchedAllocation(matchedTraffic, bucketValue) {
19
+ var total = 0;
20
+ for (var _i = 0, _a = matchedTraffic.allocation; _i < _a.length; _i++) {
21
+ var allocation = _a[_i];
22
+ total += allocation.percentage;
23
+ if (bucketValue <= total) {
24
+ return allocation;
25
+ }
26
+ }
27
+ return undefined;
28
+ }
29
+ function findForceFromFeature(feature, attributes, datafileReader) {
30
+ if (!feature.force) {
31
+ return undefined;
32
+ }
33
+ return feature.force.find(function (f) {
34
+ if (f.conditions) {
35
+ return allConditionsAreMatched(f.conditions, attributes);
36
+ }
37
+ if (f.segments) {
38
+ return allGroupSegmentsAreMatched(f.segments, attributes, datafileReader);
39
+ }
40
+ return false;
41
+ });
42
+ }
43
+ export function getForcedVariation(feature, attributes, datafileReader) {
44
+ var force = findForceFromFeature(feature, attributes, datafileReader);
45
+ if (!force || !force.variation) {
46
+ return undefined;
47
+ }
48
+ return feature.variations.find(function (v) { return v.value === force.variation; });
49
+ }
50
+ export function getBucketedVariation(feature, attributes, bucketValue, datafileReader) {
51
+ var matchedTraffic = getMatchedTraffic(feature.traffic, attributes, bucketValue, datafileReader);
52
+ if (!matchedTraffic) {
53
+ return undefined;
54
+ }
55
+ var allocation = getMatchedAllocation(matchedTraffic, bucketValue);
56
+ if (!allocation) {
57
+ return undefined;
58
+ }
59
+ var variationValue = allocation.variation;
60
+ var variation = feature.variations.find(function (v) {
61
+ return v.value === variationValue;
62
+ });
63
+ if (!variation) {
64
+ return undefined;
65
+ }
66
+ return variation;
67
+ }
68
+ export function getForcedVariableValue(feature, variableKey, attributes, datafileReader) {
69
+ var force = findForceFromFeature(feature, attributes, datafileReader);
70
+ if (!force || !force.variables) {
71
+ return undefined;
72
+ }
73
+ return force.variables[variableKey];
74
+ }
75
+ export function getBucketedVariableValue(feature, variableKey, attributes, bucketValue, datafileReader) {
76
+ var _a;
77
+ // all variables
78
+ var variablesSchema = feature.variablesSchema;
79
+ if (!variablesSchema) {
80
+ return undefined;
81
+ }
82
+ // single variable
83
+ var variableSchema = variablesSchema.find(function (v) {
84
+ v.key === variableKey;
85
+ });
86
+ if (variableSchema) {
87
+ return undefined;
88
+ }
89
+ var variation = getBucketedVariation(feature, attributes, bucketValue, datafileReader);
90
+ if (!variation) {
91
+ return undefined;
92
+ }
93
+ var variableFromVariation = (_a = variation.variables) === null || _a === void 0 ? void 0 : _a.find(function (v) {
94
+ return v.key === variableKey;
95
+ });
96
+ if (!variableFromVariation) {
97
+ return undefined;
98
+ }
99
+ if (variableFromVariation.overrides) {
100
+ var override = variableFromVariation.overrides.find(function (o) {
101
+ if (o.conditions) {
102
+ return allConditionsAreMatched(typeof o.conditions === "string" ? JSON.parse(o.conditions) : o.conditions, attributes);
103
+ }
104
+ if (o.segments) {
105
+ return allGroupSegmentsAreMatched(typeof o.segments === "string" && o.segments !== "*"
106
+ ? JSON.parse(o.segments)
107
+ : o.segments, attributes, datafileReader);
108
+ }
109
+ return false;
110
+ });
111
+ if (override) {
112
+ return override.value;
113
+ }
114
+ }
115
+ return variableFromVariation.value;
116
+ }
117
+ //# sourceMappingURL=feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature.js","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./bucket";
2
+ export * from "./client";
package/lib/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./bucket";
2
+ export * from "./client";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
File without changes
@@ -0,0 +1,4 @@
1
+ import { Attributes, GroupSegment, Segment } from "@featurevisor/types";
2
+ import { DatafileReader } from "./datafileReader";
3
+ export declare function segmentIsMatched(segment: Segment, attributes: Attributes): boolean;
4
+ export declare function allGroupSegmentsAreMatched(groupSegments: GroupSegment | GroupSegment[] | "*", attributes: Attributes, datafileReader: DatafileReader): boolean;
@@ -0,0 +1,35 @@
1
+ import { allConditionsAreMatched } from "./conditions";
2
+ export function segmentIsMatched(segment, attributes) {
3
+ return allConditionsAreMatched(segment.conditions, attributes);
4
+ }
5
+ export function allGroupSegmentsAreMatched(groupSegments, attributes, datafileReader) {
6
+ if (groupSegments === "*") {
7
+ return true;
8
+ }
9
+ if (typeof groupSegments === "string") {
10
+ var segment = datafileReader.getSegment(groupSegments);
11
+ if (segment) {
12
+ return segmentIsMatched(segment, attributes);
13
+ }
14
+ return false;
15
+ }
16
+ if (typeof groupSegments === "object") {
17
+ if ("and" in groupSegments && Array.isArray(groupSegments.and)) {
18
+ return groupSegments.and.every(function (groupSegment) {
19
+ return allGroupSegmentsAreMatched(groupSegment, attributes, datafileReader);
20
+ });
21
+ }
22
+ if ("or" in groupSegments && Array.isArray(groupSegments.or)) {
23
+ return groupSegments.or.some(function (groupSegment) {
24
+ return allGroupSegmentsAreMatched(groupSegment, attributes, datafileReader);
25
+ });
26
+ }
27
+ }
28
+ if (Array.isArray(groupSegments)) {
29
+ return groupSegments.every(function (groupSegment) {
30
+ return allGroupSegmentsAreMatched(groupSegment, attributes, datafileReader);
31
+ });
32
+ }
33
+ return false;
34
+ }
35
+ //# sourceMappingURL=segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segments.js","sourceRoot":"","sources":["../src/segments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,UAAU,gBAAgB,CAAC,OAAgB,EAAE,UAAsB;IACvE,OAAO,uBAAuB,CAAC,OAAO,CAAC,UAAqC,EAAE,UAAU,CAAC,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,aAAkD,EAClD,UAAsB,EACtB,cAA8B;IAE9B,IAAI,aAAa,KAAK,GAAG,EAAE;QACzB,OAAO,IAAI,CAAC;KACb;IAED,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE;QACrC,IAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;QAEzD,IAAI,OAAO,EAAE;YACX,OAAO,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;SAC9C;QAED,OAAO,KAAK,CAAC;KACd;IAED,IAAI,OAAO,aAAa,KAAK,QAAQ,EAAE;QACrC,IAAI,KAAK,IAAI,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE;YAC9D,OAAO,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,UAAC,YAAY;gBAC1C,OAAA,0BAA0B,CAAC,YAAY,EAAE,UAAU,EAAE,cAAc,CAAC;YAApE,CAAoE,CACrE,CAAC;SACH;QAED,IAAI,IAAI,IAAI,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE;YAC5D,OAAO,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,UAAC,YAAY;gBACxC,OAAA,0BAA0B,CAAC,YAAY,EAAE,UAAU,EAAE,cAAc,CAAC;YAApE,CAAoE,CACrE,CAAC;SACH;KACF;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE;QAChC,OAAO,aAAa,CAAC,KAAK,CAAC,UAAC,YAAY;YACtC,OAAA,0BAA0B,CAAC,YAAY,EAAE,UAAU,EAAE,cAAc,CAAC;QAApE,CAAoE,CACrE,CAAC;KACH;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@featurevisor/sdk",
3
+ "version": "0.0.3",
4
+ "description": "Featurevisor SDK for Node.js and the browser",
5
+ "main": "dist/index.js",
6
+ "module": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "scripts": {
9
+ "lint": "echo 'not linting in this package yet'",
10
+ "transpile": "rimraf lib && tsc --project tsconfig.esm.json",
11
+ "dist": "webpack --config ./webpack.config.js",
12
+ "build": "npm run transpile && npm run dist",
13
+ "test": "jest --config ../../jest.config.js --verbose"
14
+ },
15
+ "author": {
16
+ "name": "Fahad Heylaal",
17
+ "url": "https://fahad19.com"
18
+ },
19
+ "homepage": "https://featurevisor.com",
20
+ "keywords": [
21
+ "featurevisor",
22
+ "feature",
23
+ "features",
24
+ "flags",
25
+ "feature flags",
26
+ "feature toggles",
27
+ "feature management",
28
+ "experimentation",
29
+ "experiment",
30
+ "experiments"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/fahad19/featurevisor.git"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/fahad19/featurevisor/issues"
42
+ },
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@featurevisor/types": "^0.0.3",
46
+ "murmurhash": "^2.0.1"
47
+ },
48
+ "gitHead": "303aca84f7c32163cb1079513d799296bd892e72"
49
+ }
@@ -0,0 +1,18 @@
1
+ import { getBucketedNumber, MAX_BUCKETED_NUMBER } from "./bucket";
2
+
3
+ describe("sdk: Bucket", function () {
4
+ it("should be a function", function () {
5
+ expect(typeof getBucketedNumber).toEqual("function");
6
+ });
7
+
8
+ it("should return a number between 0 and 100000", function () {
9
+ const keys = ["foo", "bar", "baz", "123adshlk348-93asdlk"];
10
+
11
+ keys.forEach((key) => {
12
+ const n = getBucketedNumber(key);
13
+
14
+ expect(n >= 0).toEqual(true);
15
+ expect(n <= MAX_BUCKETED_NUMBER).toEqual(true);
16
+ });
17
+ });
18
+ });
package/src/bucket.ts ADDED
@@ -0,0 +1,13 @@
1
+ import * as murmurhash from "murmurhash";
2
+
3
+ const HASH_SEED = 1;
4
+ const MAX_HASH_VALUE = Math.pow(2, 32);
5
+
6
+ export const MAX_BUCKETED_NUMBER = 100000; // 100% * 1000 to include three decimal places in the same integer value
7
+
8
+ export function getBucketedNumber(bucketKey: string): number {
9
+ const hashValue = murmurhash.v3(bucketKey, HASH_SEED);
10
+ const ratio = hashValue / MAX_HASH_VALUE;
11
+
12
+ return Math.floor(ratio * MAX_BUCKETED_NUMBER);
13
+ }
package/src/client.ts ADDED
@@ -0,0 +1,338 @@
1
+ import {
2
+ Attributes,
3
+ VariationValue,
4
+ VariableValue,
5
+ Feature,
6
+ DatafileContent,
7
+ BucketKey,
8
+ BucketValue,
9
+ FeatureKey,
10
+ } from "@featurevisor/types";
11
+ import { DatafileReader } from "./datafileReader";
12
+ import {
13
+ getBucketedVariation,
14
+ getBucketedVariableValue,
15
+ getForcedVariation,
16
+ getForcedVariableValue,
17
+ } from "./feature";
18
+ import { getBucketedNumber } from "./bucket";
19
+
20
+ export type ActivationCallback = (
21
+ featureName: string,
22
+ variation: VariationValue,
23
+ attributes: Attributes,
24
+ captureAttributes: Attributes,
25
+ ) => void;
26
+
27
+ export type ConfigureBucketValue = (feature, attributes, bucketValue: BucketValue) => BucketValue;
28
+ export interface SdkOptions {
29
+ datafile: DatafileContent | string;
30
+ onActivation?: ActivationCallback;
31
+ configureBucketValue?: ConfigureBucketValue;
32
+ }
33
+
34
+ // union of VariableValue and VariationValue
35
+ type FieldType = "string" | "integer" | "double" | "boolean" | "array";
36
+ type ValueType = string | number | boolean | string[] | null | undefined;
37
+
38
+ export function getValueByType(value: ValueType, fieldType: FieldType): ValueType {
39
+ if (value === undefined) {
40
+ return undefined;
41
+ }
42
+
43
+ switch (fieldType) {
44
+ case "string":
45
+ return typeof value === "string" ? value : undefined;
46
+ case "integer":
47
+ return parseInt(value as string, 10);
48
+ case "double":
49
+ return parseFloat(value as string);
50
+ case "boolean":
51
+ return value === true;
52
+ case "array":
53
+ return Array.isArray(value) ? value : undefined;
54
+ default:
55
+ return value;
56
+ }
57
+ }
58
+
59
+ export class FeaturevisorSDK {
60
+ private onActivation?: ActivationCallback;
61
+ private datafileReader: DatafileReader;
62
+ private configureBucketValue?: ConfigureBucketValue;
63
+
64
+ constructor(options: SdkOptions) {
65
+ if (options.onActivation) {
66
+ this.onActivation = options.onActivation;
67
+ }
68
+
69
+ if (options.configureBucketValue) {
70
+ this.configureBucketValue = options.configureBucketValue;
71
+ }
72
+
73
+ try {
74
+ this.datafileReader = new DatafileReader(
75
+ typeof options.datafile === "string" ? JSON.parse(options.datafile) : options.datafile,
76
+ );
77
+ } catch (e) {
78
+ console.error(`Featurevisor could not parse the datafile`);
79
+ console.error(e);
80
+ }
81
+ }
82
+
83
+ private getFeature(featureKey: string | Feature): Feature | undefined {
84
+ return typeof featureKey === "string"
85
+ ? this.datafileReader.getFeature(featureKey) // only key provided
86
+ : featureKey; // full feature provided
87
+ }
88
+
89
+ /**
90
+ * Bucketing
91
+ */
92
+
93
+ private getBucketKey(feature: Feature, attributes: Attributes): BucketKey {
94
+ const featureKey = feature.key;
95
+
96
+ const prefix =
97
+ typeof feature.bucketBy === "string" ? feature.bucketBy : feature.bucketBy.join("_");
98
+
99
+ return `${prefix}_${featureKey}`;
100
+ }
101
+
102
+ private getBucketValue(feature: Feature, attributes: Attributes): BucketValue {
103
+ const bucketKey = this.getBucketKey(feature, attributes);
104
+
105
+ const value = getBucketedNumber(bucketKey);
106
+
107
+ if (this.configureBucketValue) {
108
+ return this.configureBucketValue(feature, attributes, value);
109
+ }
110
+
111
+ return value;
112
+ }
113
+
114
+ /**
115
+ * Variation
116
+ */
117
+
118
+ getVariation(
119
+ featureKey: FeatureKey | Feature,
120
+ attributes: Attributes = {},
121
+ ): VariationValue | undefined {
122
+ try {
123
+ const feature = this.getFeature(featureKey);
124
+
125
+ if (!feature) {
126
+ return undefined;
127
+ }
128
+
129
+ const forcedVariation = getForcedVariation(feature, attributes, this.datafileReader);
130
+
131
+ if (forcedVariation) {
132
+ return forcedVariation.value;
133
+ }
134
+
135
+ const bucketValue = this.getBucketValue(feature, attributes);
136
+
137
+ const variation = getBucketedVariation(feature, attributes, bucketValue, this.datafileReader);
138
+
139
+ if (!variation) {
140
+ return undefined;
141
+ }
142
+
143
+ return variation.value;
144
+ } catch (e) {
145
+ console.error("[Featurevisor]", e);
146
+
147
+ return undefined;
148
+ }
149
+ }
150
+
151
+ getVariationBoolean(
152
+ featureKey: FeatureKey | Feature,
153
+ attributes: Attributes = {},
154
+ ): boolean | undefined {
155
+ const variationValue = this.getVariation(featureKey, attributes);
156
+
157
+ return getValueByType(variationValue, "boolean") as boolean | undefined;
158
+ }
159
+
160
+ getVariationString(
161
+ featureKey: FeatureKey | Feature,
162
+ attributes: Attributes = {},
163
+ ): string | undefined {
164
+ const variationValue = this.getVariation(featureKey, attributes);
165
+
166
+ return getValueByType(variationValue, "string") as string | undefined;
167
+ }
168
+
169
+ getVariationInteger(
170
+ featureKey: FeatureKey | Feature,
171
+ attributes: Attributes = {},
172
+ ): number | undefined {
173
+ const variationValue = this.getVariation(featureKey, attributes);
174
+
175
+ return getValueByType(variationValue, "integer") as number | undefined;
176
+ }
177
+
178
+ getVariationDouble(
179
+ featureKey: FeatureKey | Feature,
180
+ attributes: Attributes = {},
181
+ ): number | undefined {
182
+ const variationValue = this.getVariation(featureKey, attributes);
183
+
184
+ return getValueByType(variationValue, "double") as number | undefined;
185
+ }
186
+
187
+ /**
188
+ * Activate
189
+ */
190
+ activate(featureKey: FeatureKey, attributes: Attributes = {}): VariationValue | undefined {
191
+ try {
192
+ const variationValue = this.getVariation(featureKey, attributes);
193
+
194
+ if (!variationValue) {
195
+ return undefined;
196
+ }
197
+
198
+ if (this.onActivation) {
199
+ const captureAttributes: Attributes = {};
200
+
201
+ const attributesForCapturing = this.datafileReader
202
+ .getAllAttributes()
203
+ .filter((a) => a.capture === true);
204
+
205
+ attributesForCapturing.forEach((a) => {
206
+ if (typeof attributes[a.key] !== "undefined") {
207
+ captureAttributes[a.key] = attributes[a.key];
208
+ }
209
+ });
210
+
211
+ this.onActivation(featureKey, variationValue, attributes, captureAttributes);
212
+ }
213
+
214
+ return variationValue;
215
+ } catch (e) {
216
+ console.error("[Featurevisor]", e);
217
+
218
+ return undefined;
219
+ }
220
+ }
221
+
222
+ activateBoolean(featureKey: FeatureKey, attributes: Attributes = {}): boolean | undefined {
223
+ const variationValue = this.activate(featureKey, attributes);
224
+
225
+ return getValueByType(variationValue, "boolean") as boolean | undefined;
226
+ }
227
+
228
+ activateString(featureKey: FeatureKey, attributes: Attributes = {}): string | undefined {
229
+ const variationValue = this.activate(featureKey, attributes);
230
+
231
+ return getValueByType(variationValue, "string") as string | undefined;
232
+ }
233
+
234
+ activateInteger(featureKey: FeatureKey, attributes: Attributes = {}): number | undefined {
235
+ const variationValue = this.activate(featureKey, attributes);
236
+
237
+ return getValueByType(variationValue, "integer") as number | undefined;
238
+ }
239
+
240
+ activateDouble(featureKey: FeatureKey, attributes: Attributes = {}): number | undefined {
241
+ const variationValue = this.activate(featureKey, attributes);
242
+
243
+ return getValueByType(variationValue, "double") as number | undefined;
244
+ }
245
+
246
+ /**
247
+ * Variable
248
+ */
249
+
250
+ getVariable(
251
+ featureKey: FeatureKey | Feature,
252
+ variableKey: string,
253
+ attributes: Attributes = {},
254
+ ): VariableValue | undefined {
255
+ try {
256
+ const feature = this.getFeature(featureKey);
257
+
258
+ if (!feature) {
259
+ return undefined;
260
+ }
261
+
262
+ const forcedVariableValue = getForcedVariableValue(
263
+ feature,
264
+ variableKey,
265
+ attributes,
266
+ this.datafileReader,
267
+ );
268
+
269
+ if (typeof forcedVariableValue !== "undefined") {
270
+ return forcedVariableValue;
271
+ }
272
+
273
+ const bucketValue = this.getBucketValue(feature, attributes);
274
+
275
+ return getBucketedVariableValue(
276
+ feature,
277
+ variableKey,
278
+ attributes,
279
+ bucketValue,
280
+ this.datafileReader,
281
+ );
282
+ } catch (e) {
283
+ console.error("[Featurevisor]", e);
284
+
285
+ return undefined;
286
+ }
287
+ }
288
+
289
+ getVariableBoolean(
290
+ featureKey: FeatureKey | Feature,
291
+ variableKey: string,
292
+ attributes: Attributes = {},
293
+ ): boolean | undefined {
294
+ const variableValue = this.getVariable(featureKey, variableKey, attributes);
295
+
296
+ return getValueByType(variableValue, "boolean") as boolean | undefined;
297
+ }
298
+
299
+ getVariableString(
300
+ featureKey: FeatureKey | Feature,
301
+ variableKey: string,
302
+ attributes: Attributes = {},
303
+ ): string | undefined {
304
+ const variableValue = this.getVariable(featureKey, variableKey, attributes);
305
+
306
+ return getValueByType(variableValue, "string") as string | undefined;
307
+ }
308
+
309
+ getVariableInteger(
310
+ featureKey: FeatureKey | Feature,
311
+ variableKey: string,
312
+ attributes: Attributes = {},
313
+ ): number | undefined {
314
+ const variableValue = this.getVariable(featureKey, variableKey, attributes);
315
+
316
+ return getValueByType(variableValue, "integer") as number | undefined;
317
+ }
318
+
319
+ getVariableDouble(
320
+ featureKey: FeatureKey | Feature,
321
+ variableKey: string,
322
+ attributes: Attributes = {},
323
+ ): number | undefined {
324
+ const variableValue = this.getVariable(featureKey, variableKey, attributes);
325
+
326
+ return getValueByType(variableValue, "double") as number | undefined;
327
+ }
328
+
329
+ getVariableArray(
330
+ featureKey: FeatureKey | Feature,
331
+ variableKey: string,
332
+ attributes: Attributes = {},
333
+ ): string[] | undefined {
334
+ const variableValue = this.getVariable(featureKey, variableKey, attributes);
335
+
336
+ return getValueByType(variableValue, "array") as string[] | undefined;
337
+ }
338
+ }