@featurevisor/sdk 1.35.3 → 2.0.1
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/CHANGELOG.md +8 -0
- package/README.md +2 -381
- package/coverage/clover.xml +707 -645
- package/coverage/coverage-final.json +11 -9
- package/coverage/lcov-report/{segments.ts.html → bucketer.ts.html} +155 -77
- package/coverage/lcov-report/child.ts.html +940 -0
- package/coverage/lcov-report/conditions.ts.html +107 -158
- package/coverage/lcov-report/datafileReader.ts.html +763 -103
- package/coverage/lcov-report/emitter.ts.html +77 -59
- package/coverage/lcov-report/evaluate.ts.html +689 -416
- package/coverage/lcov-report/events.ts.html +334 -0
- package/coverage/lcov-report/helpers.ts.html +184 -0
- package/coverage/lcov-report/{bucket.ts.html → hooks.ts.html} +86 -239
- package/coverage/lcov-report/index.html +119 -89
- package/coverage/lcov-report/instance.ts.html +341 -773
- package/coverage/lcov-report/logger.ts.html +64 -64
- package/coverage/lcov.info +1433 -1226
- package/dist/bucketer.d.ts +11 -0
- package/dist/child.d.ts +26 -0
- package/dist/compareVersions.d.ts +4 -0
- package/dist/conditions.d.ts +4 -4
- package/dist/datafileReader.d.ts +26 -6
- package/dist/emitter.d.ts +8 -9
- package/dist/evaluate.d.ts +31 -29
- package/dist/events.d.ts +5 -0
- package/dist/helpers.d.ts +5 -0
- package/dist/hooks.d.ts +45 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.gz +0 -0
- package/dist/index.mjs.map +1 -1
- package/dist/instance.d.ts +40 -72
- package/dist/logger.d.ts +6 -5
- package/dist/murmurhash.d.ts +1 -0
- package/jest.config.js +2 -0
- package/lib/bucketer.d.ts +11 -0
- package/lib/child.d.ts +26 -0
- package/lib/compareVersions.d.ts +4 -0
- package/lib/conditions.d.ts +4 -4
- package/lib/datafileReader.d.ts +26 -6
- package/lib/emitter.d.ts +8 -9
- package/lib/evaluate.d.ts +31 -29
- package/lib/events.d.ts +5 -0
- package/lib/helpers.d.ts +5 -0
- package/lib/hooks.d.ts +45 -0
- package/lib/index.d.ts +3 -2
- package/lib/instance.d.ts +40 -72
- package/lib/logger.d.ts +6 -5
- package/lib/murmurhash.d.ts +1 -0
- package/package.json +3 -5
- package/src/bucketer.spec.ts +165 -0
- package/src/bucketer.ts +84 -0
- package/src/child.spec.ts +267 -0
- package/src/child.ts +285 -0
- package/src/compareVersions.ts +93 -0
- package/src/conditions.spec.ts +563 -353
- package/src/conditions.ts +46 -63
- package/src/datafileReader.spec.ts +396 -84
- package/src/datafileReader.ts +280 -60
- package/src/emitter.spec.ts +27 -86
- package/src/emitter.ts +38 -32
- package/src/evaluate.ts +349 -258
- package/src/events.spec.ts +154 -0
- package/src/events.ts +83 -0
- package/src/helpers.ts +33 -0
- package/src/hooks.ts +88 -0
- package/src/index.ts +3 -2
- package/src/instance.spec.ts +305 -489
- package/src/instance.ts +247 -391
- package/src/logger.spec.ts +212 -134
- package/src/logger.ts +36 -36
- package/src/murmurhash.ts +71 -0
- package/coverage/lcov-report/feature.ts.html +0 -508
- package/dist/bucket.d.ts +0 -30
- package/dist/feature.d.ts +0 -16
- package/dist/segments.d.ts +0 -5
- package/lib/bucket.d.ts +0 -30
- package/lib/feature.d.ts +0 -16
- package/lib/segments.d.ts +0 -5
- package/src/bucket.spec.ts +0 -37
- package/src/bucket.ts +0 -139
- package/src/feature.ts +0 -141
- package/src/segments.spec.ts +0 -468
- package/src/segments.ts +0 -58
package/src/bucket.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import * as murmurhash from "murmurhash";
|
|
2
|
-
import { Feature, BucketKey, BucketValue, Context, AttributeValue } from "@featurevisor/types";
|
|
3
|
-
|
|
4
|
-
import { Logger } from "./logger";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generic hashing
|
|
8
|
-
*/
|
|
9
|
-
const HASH_SEED = 1;
|
|
10
|
-
const MAX_HASH_VALUE = Math.pow(2, 32);
|
|
11
|
-
|
|
12
|
-
export const MAX_BUCKETED_NUMBER = 100000; // 100% * 1000 to include three decimal places in the same integer value
|
|
13
|
-
|
|
14
|
-
export function getBucketedNumber(bucketKey: string): number {
|
|
15
|
-
const hashValue = murmurhash.v3(bucketKey, HASH_SEED);
|
|
16
|
-
const ratio = hashValue / MAX_HASH_VALUE;
|
|
17
|
-
|
|
18
|
-
return Math.floor(ratio * MAX_BUCKETED_NUMBER);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Feature specific bucketing
|
|
23
|
-
*/
|
|
24
|
-
export type ConfigureBucketKey = (
|
|
25
|
-
feature: Feature,
|
|
26
|
-
context: Context,
|
|
27
|
-
bucketKey: BucketKey,
|
|
28
|
-
) => BucketKey;
|
|
29
|
-
|
|
30
|
-
export interface BucketKeyOptions {
|
|
31
|
-
feature: Feature;
|
|
32
|
-
context: Context;
|
|
33
|
-
logger: Logger;
|
|
34
|
-
bucketKeySeparator?: string;
|
|
35
|
-
configureBucketKey?: ConfigureBucketKey;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getBucketKey(options: BucketKeyOptions): BucketKey {
|
|
39
|
-
const { feature, context, logger, bucketKeySeparator = ".", configureBucketKey } = options;
|
|
40
|
-
|
|
41
|
-
const featureKey = feature.key;
|
|
42
|
-
|
|
43
|
-
let type;
|
|
44
|
-
let attributeKeys;
|
|
45
|
-
|
|
46
|
-
if (typeof feature.bucketBy === "string") {
|
|
47
|
-
type = "plain";
|
|
48
|
-
attributeKeys = [feature.bucketBy];
|
|
49
|
-
} else if (Array.isArray(feature.bucketBy)) {
|
|
50
|
-
type = "and";
|
|
51
|
-
attributeKeys = feature.bucketBy;
|
|
52
|
-
} else if (typeof feature.bucketBy === "object" && Array.isArray(feature.bucketBy.or)) {
|
|
53
|
-
type = "or";
|
|
54
|
-
attributeKeys = feature.bucketBy.or;
|
|
55
|
-
} else {
|
|
56
|
-
logger.error("invalid bucketBy", { featureKey, bucketBy: feature.bucketBy });
|
|
57
|
-
|
|
58
|
-
throw new Error("invalid bucketBy");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const bucketKey: AttributeValue[] = [];
|
|
62
|
-
|
|
63
|
-
attributeKeys.forEach((attributeKey) => {
|
|
64
|
-
const attributeValue = context[attributeKey];
|
|
65
|
-
|
|
66
|
-
if (typeof attributeValue === "undefined") {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (type === "plain" || type === "and") {
|
|
71
|
-
bucketKey.push(attributeValue);
|
|
72
|
-
} else {
|
|
73
|
-
// or
|
|
74
|
-
if (bucketKey.length === 0) {
|
|
75
|
-
bucketKey.push(attributeValue);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
bucketKey.push(featureKey);
|
|
81
|
-
|
|
82
|
-
const result = bucketKey.join(bucketKeySeparator);
|
|
83
|
-
|
|
84
|
-
if (configureBucketKey) {
|
|
85
|
-
return configureBucketKey(feature, context, result);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface Bucket {
|
|
92
|
-
bucketKey: BucketKey;
|
|
93
|
-
bucketValue: BucketValue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type ConfigureBucketValue = (
|
|
97
|
-
feature: Feature,
|
|
98
|
-
context: Context,
|
|
99
|
-
bucketValue: BucketValue,
|
|
100
|
-
) => BucketValue;
|
|
101
|
-
|
|
102
|
-
export interface BucketValueOptions {
|
|
103
|
-
// common with BucketKeyOptions
|
|
104
|
-
feature: Feature;
|
|
105
|
-
context: Context;
|
|
106
|
-
logger: Logger;
|
|
107
|
-
bucketKeySeparator?: string;
|
|
108
|
-
configureBucketKey?: ConfigureBucketKey;
|
|
109
|
-
|
|
110
|
-
// specific to BucketValueOptions
|
|
111
|
-
configureBucketValue?: ConfigureBucketValue;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function getBucket(options: BucketValueOptions): Bucket {
|
|
115
|
-
const { feature, context, logger, bucketKeySeparator, configureBucketKey, configureBucketValue } =
|
|
116
|
-
options;
|
|
117
|
-
const bucketKey = getBucketKey({
|
|
118
|
-
feature,
|
|
119
|
-
context,
|
|
120
|
-
logger,
|
|
121
|
-
bucketKeySeparator,
|
|
122
|
-
configureBucketKey,
|
|
123
|
-
});
|
|
124
|
-
const value = getBucketedNumber(bucketKey);
|
|
125
|
-
|
|
126
|
-
if (configureBucketValue) {
|
|
127
|
-
const configuredValue = configureBucketValue(feature, context, value);
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
bucketKey,
|
|
131
|
-
bucketValue: configuredValue,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
bucketKey,
|
|
137
|
-
bucketValue: value,
|
|
138
|
-
};
|
|
139
|
-
}
|
package/src/feature.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Allocation, Context, Traffic, Feature, Force } from "@featurevisor/types";
|
|
2
|
-
import { DatafileReader } from "./datafileReader";
|
|
3
|
-
import { allGroupSegmentsAreMatched } from "./segments";
|
|
4
|
-
import { allConditionsAreMatched } from "./conditions";
|
|
5
|
-
import { Logger } from "./logger";
|
|
6
|
-
|
|
7
|
-
export function getMatchedAllocation(
|
|
8
|
-
traffic: Traffic,
|
|
9
|
-
bucketValue: number,
|
|
10
|
-
): Allocation | undefined {
|
|
11
|
-
if (!traffic.allocation) {
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
for (const allocation of traffic.allocation) {
|
|
16
|
-
const [start, end] = allocation.range;
|
|
17
|
-
|
|
18
|
-
if (allocation.range && start <= bucketValue && end >= bucketValue) {
|
|
19
|
-
return allocation;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function parseFromStringifiedSegments(value) {
|
|
27
|
-
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
28
|
-
return JSON.parse(value);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getMatchedTraffic(
|
|
35
|
-
traffic: Traffic[],
|
|
36
|
-
context: Context,
|
|
37
|
-
datafileReader: DatafileReader,
|
|
38
|
-
logger: Logger,
|
|
39
|
-
): Traffic | undefined {
|
|
40
|
-
const matchedTraffic = traffic.find((t) => {
|
|
41
|
-
if (
|
|
42
|
-
!allGroupSegmentsAreMatched(
|
|
43
|
-
parseFromStringifiedSegments(t.segments),
|
|
44
|
-
context,
|
|
45
|
-
datafileReader,
|
|
46
|
-
logger,
|
|
47
|
-
)
|
|
48
|
-
) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
if (matchedTraffic && matchedTraffic.percentage > 0) {
|
|
56
|
-
return matchedTraffic;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface MatchedTrafficAndAllocation {
|
|
63
|
-
matchedTraffic: Traffic | undefined;
|
|
64
|
-
matchedAllocation: Allocation | undefined;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getMatchedTrafficAndAllocation(
|
|
68
|
-
traffic: Traffic[],
|
|
69
|
-
context: Context,
|
|
70
|
-
bucketValue: number,
|
|
71
|
-
datafileReader: DatafileReader,
|
|
72
|
-
logger: Logger,
|
|
73
|
-
): MatchedTrafficAndAllocation {
|
|
74
|
-
const matchedTraffic = traffic.find((t) => {
|
|
75
|
-
return allGroupSegmentsAreMatched(
|
|
76
|
-
parseFromStringifiedSegments(t.segments),
|
|
77
|
-
context,
|
|
78
|
-
datafileReader,
|
|
79
|
-
logger,
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
if (!matchedTraffic) {
|
|
84
|
-
return {
|
|
85
|
-
matchedTraffic: undefined,
|
|
86
|
-
matchedAllocation: undefined,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const matchedAllocation = getMatchedAllocation(matchedTraffic, bucketValue);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
matchedTraffic,
|
|
94
|
-
matchedAllocation,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface ForceResult {
|
|
99
|
-
force?: Force;
|
|
100
|
-
forceIndex?: number;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function findForceFromFeature(
|
|
104
|
-
feature: Feature,
|
|
105
|
-
context: Context,
|
|
106
|
-
datafileReader: DatafileReader,
|
|
107
|
-
logger: Logger,
|
|
108
|
-
): ForceResult {
|
|
109
|
-
const result: ForceResult = {
|
|
110
|
-
force: undefined,
|
|
111
|
-
forceIndex: undefined,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (!feature.force) {
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (let i = 0; i < feature.force.length; i++) {
|
|
119
|
-
const currentForce = feature.force[i];
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
currentForce.conditions &&
|
|
123
|
-
allConditionsAreMatched(currentForce.conditions, context, logger)
|
|
124
|
-
) {
|
|
125
|
-
result.force = currentForce;
|
|
126
|
-
result.forceIndex = i;
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
currentForce.segments &&
|
|
132
|
-
allGroupSegmentsAreMatched(currentForce.segments, context, datafileReader, logger)
|
|
133
|
-
) {
|
|
134
|
-
result.force = currentForce;
|
|
135
|
-
result.forceIndex = i;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return result;
|
|
141
|
-
}
|