@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -381
  3. package/coverage/clover.xml +707 -645
  4. package/coverage/coverage-final.json +11 -9
  5. package/coverage/lcov-report/{segments.ts.html → bucketer.ts.html} +155 -77
  6. package/coverage/lcov-report/child.ts.html +940 -0
  7. package/coverage/lcov-report/conditions.ts.html +107 -158
  8. package/coverage/lcov-report/datafileReader.ts.html +763 -103
  9. package/coverage/lcov-report/emitter.ts.html +77 -59
  10. package/coverage/lcov-report/evaluate.ts.html +689 -416
  11. package/coverage/lcov-report/events.ts.html +334 -0
  12. package/coverage/lcov-report/helpers.ts.html +184 -0
  13. package/coverage/lcov-report/{bucket.ts.html → hooks.ts.html} +86 -239
  14. package/coverage/lcov-report/index.html +119 -89
  15. package/coverage/lcov-report/instance.ts.html +341 -773
  16. package/coverage/lcov-report/logger.ts.html +64 -64
  17. package/coverage/lcov.info +1433 -1226
  18. package/dist/bucketer.d.ts +11 -0
  19. package/dist/child.d.ts +26 -0
  20. package/dist/compareVersions.d.ts +4 -0
  21. package/dist/conditions.d.ts +4 -4
  22. package/dist/datafileReader.d.ts +26 -6
  23. package/dist/emitter.d.ts +8 -9
  24. package/dist/evaluate.d.ts +31 -29
  25. package/dist/events.d.ts +5 -0
  26. package/dist/helpers.d.ts +5 -0
  27. package/dist/hooks.d.ts +45 -0
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1 -1
  32. package/dist/index.mjs.gz +0 -0
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/instance.d.ts +40 -72
  35. package/dist/logger.d.ts +6 -5
  36. package/dist/murmurhash.d.ts +1 -0
  37. package/jest.config.js +2 -0
  38. package/lib/bucketer.d.ts +11 -0
  39. package/lib/child.d.ts +26 -0
  40. package/lib/compareVersions.d.ts +4 -0
  41. package/lib/conditions.d.ts +4 -4
  42. package/lib/datafileReader.d.ts +26 -6
  43. package/lib/emitter.d.ts +8 -9
  44. package/lib/evaluate.d.ts +31 -29
  45. package/lib/events.d.ts +5 -0
  46. package/lib/helpers.d.ts +5 -0
  47. package/lib/hooks.d.ts +45 -0
  48. package/lib/index.d.ts +3 -2
  49. package/lib/instance.d.ts +40 -72
  50. package/lib/logger.d.ts +6 -5
  51. package/lib/murmurhash.d.ts +1 -0
  52. package/package.json +3 -5
  53. package/src/bucketer.spec.ts +165 -0
  54. package/src/bucketer.ts +84 -0
  55. package/src/child.spec.ts +267 -0
  56. package/src/child.ts +285 -0
  57. package/src/compareVersions.ts +93 -0
  58. package/src/conditions.spec.ts +563 -353
  59. package/src/conditions.ts +46 -63
  60. package/src/datafileReader.spec.ts +396 -84
  61. package/src/datafileReader.ts +280 -60
  62. package/src/emitter.spec.ts +27 -86
  63. package/src/emitter.ts +38 -32
  64. package/src/evaluate.ts +349 -258
  65. package/src/events.spec.ts +154 -0
  66. package/src/events.ts +83 -0
  67. package/src/helpers.ts +33 -0
  68. package/src/hooks.ts +88 -0
  69. package/src/index.ts +3 -2
  70. package/src/instance.spec.ts +305 -489
  71. package/src/instance.ts +247 -391
  72. package/src/logger.spec.ts +212 -134
  73. package/src/logger.ts +36 -36
  74. package/src/murmurhash.ts +71 -0
  75. package/coverage/lcov-report/feature.ts.html +0 -508
  76. package/dist/bucket.d.ts +0 -30
  77. package/dist/feature.d.ts +0 -16
  78. package/dist/segments.d.ts +0 -5
  79. package/lib/bucket.d.ts +0 -30
  80. package/lib/feature.d.ts +0 -16
  81. package/lib/segments.d.ts +0 -5
  82. package/src/bucket.spec.ts +0 -37
  83. package/src/bucket.ts +0 -139
  84. package/src/feature.ts +0 -141
  85. package/src/segments.spec.ts +0 -468
  86. 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
- }