featureflow-node-sdk 0.6.4 → 0.6.8
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 +6 -0
- package/README.md +10 -0
- package/dist/Client.js +28 -0
- package/dist/Conditions.js +22 -2
- package/dist/Evaluate.js +3 -2
- package/dist/EvaluateHelpers.js +9 -1
- package/dist/EventsClient.js +87 -54
- package/dist/FeatureStore.js +5 -0
- package/dist/PollingClient.js +12 -9
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[![][npm-img]][npm-url]
|
|
4
4
|
|
|
5
|
+
[](https://circleci.com/gh/featureflow/featureflow-node-sdk)
|
|
6
|
+
|
|
5
7
|
[![][dependency-img]][dependency-url]
|
|
6
8
|
|
|
7
9
|
> Featureflow Node SDK
|
|
@@ -12,6 +14,14 @@ Get your Featureflow account at [featureflow.io](http://www.featureflow.io)
|
|
|
12
14
|
|
|
13
15
|
The easiest way to get started is to follow the [Featureflow quick start guides](http://docs.featureflow.io/docs)
|
|
14
16
|
|
|
17
|
+
## Examples
|
|
18
|
+
|
|
19
|
+
Express: [here](https://github.com/featureflow/featureflow-node-example)
|
|
20
|
+
|
|
21
|
+
NextJS: [here](https://github.com/featureflow/featureflow-example-nextjs)
|
|
22
|
+
|
|
23
|
+
5 Minute: [here](https://github.com/featureflow/featureflow-fiveminute-node) [(docs)](https://docs.featureflow.io/docs/nodejs-5-minute-test)
|
|
24
|
+
|
|
15
25
|
## Change Log
|
|
16
26
|
|
|
17
27
|
Please see [CHANGELOG](https://github.com/featureflow/featureflow-node-sdk/blob/master/CHANGELOG.md).
|
package/dist/Client.js
CHANGED
|
@@ -105,6 +105,34 @@ var Featureflow = function (_EventEmitter) {
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
}, {
|
|
109
|
+
key: 'evaluateAll',
|
|
110
|
+
value: function (_evaluateAll) {
|
|
111
|
+
function evaluateAll() {
|
|
112
|
+
return _evaluateAll.apply(this, arguments);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
evaluateAll.toString = function () {
|
|
116
|
+
return _evaluateAll.toString();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return evaluateAll;
|
|
120
|
+
}(function () {
|
|
121
|
+
return evaluateAll('anonymous');
|
|
122
|
+
})
|
|
123
|
+
}, {
|
|
124
|
+
key: 'evaluateAll',
|
|
125
|
+
value: function evaluateAll(user) {
|
|
126
|
+
var evaluatedFeatures = {};
|
|
127
|
+
var features = this.config.featureStore.getAll();
|
|
128
|
+
for (var p in features) {
|
|
129
|
+
if (features.hasOwnProperty(p)) {
|
|
130
|
+
var value = this.evaluate(p, user).value();
|
|
131
|
+
evaluatedFeatures[p] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return evaluatedFeatures;
|
|
135
|
+
}
|
|
108
136
|
}, {
|
|
109
137
|
key: 'evaluate',
|
|
110
138
|
value: function (_evaluate) {
|
package/dist/Conditions.js
CHANGED
|
@@ -27,10 +27,20 @@ var operators = {
|
|
|
27
27
|
return typeof a === 'string' && Array.isArray(b) && b.indexOf(a) < 0;
|
|
28
28
|
},
|
|
29
29
|
before: function before(a, b) {
|
|
30
|
-
|
|
30
|
+
a = dateParse(a);
|
|
31
|
+
b = dateParse(b);
|
|
32
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
33
|
+
return a < b;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
31
36
|
},
|
|
32
37
|
after: function after(a, b) {
|
|
33
|
-
|
|
38
|
+
a = dateParse(a);
|
|
39
|
+
b = dateParse(b);
|
|
40
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
41
|
+
return a > b;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
34
44
|
},
|
|
35
45
|
greaterThan: function greaterThan(a, b) {
|
|
36
46
|
return a > b;
|
|
@@ -46,6 +56,16 @@ var operators = {
|
|
|
46
56
|
}
|
|
47
57
|
};
|
|
48
58
|
|
|
59
|
+
function dateParse(date) {
|
|
60
|
+
if (typeof date === 'string') {
|
|
61
|
+
return Date.parse(date);
|
|
62
|
+
}
|
|
63
|
+
if (date instanceof Date) {
|
|
64
|
+
return date.getTime();
|
|
65
|
+
}
|
|
66
|
+
return date;
|
|
67
|
+
}
|
|
68
|
+
|
|
49
69
|
var notFound = function notFound() {
|
|
50
70
|
return false;
|
|
51
71
|
};
|
package/dist/Evaluate.js
CHANGED
|
@@ -30,13 +30,14 @@ var Evaluate = function () {
|
|
|
30
30
|
return this.is('on');
|
|
31
31
|
}
|
|
32
32
|
}, {
|
|
33
|
-
key: '
|
|
34
|
-
value: function
|
|
33
|
+
key: 'isOff',
|
|
34
|
+
value: function isOff() {
|
|
35
35
|
return this.is('off');
|
|
36
36
|
}
|
|
37
37
|
}, {
|
|
38
38
|
key: 'value',
|
|
39
39
|
value: function value() {
|
|
40
|
+
this.eventsClient.evaluateEvent(this.featureKey, this.evaluatedVariant, null, this.user);
|
|
40
41
|
return this.evaluatedVariant;
|
|
41
42
|
}
|
|
42
43
|
}]);
|
package/dist/EvaluateHelpers.js
CHANGED
|
@@ -31,6 +31,14 @@ function featureEvaluation(feature, user) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (feature.enabled) {
|
|
34
|
+
var now = new Date();
|
|
35
|
+
var hourOfDay = now.getHours();
|
|
36
|
+
var hArray = new Array(1);
|
|
37
|
+
hArray[0] = hourOfDay;
|
|
38
|
+
user.attributes['featureflow.user.id'] = [user.id];
|
|
39
|
+
user.attributes['featureflow.date'] = [new Date().toISOString()];
|
|
40
|
+
user.attributes['featureflow.hourofday'] = hArray;
|
|
41
|
+
|
|
34
42
|
for (var i in feature.rules) {
|
|
35
43
|
var rule = feature.rules[i];
|
|
36
44
|
if (ruleMatches(feature.rules[i], user)) {
|
|
@@ -44,7 +52,7 @@ function featureEvaluation(feature, user) {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
function ruleMatches(rule, user) {
|
|
47
|
-
if (rule.
|
|
55
|
+
if (!rule.audience) {
|
|
48
56
|
return true;
|
|
49
57
|
} else {
|
|
50
58
|
for (var cKey in rule.audience.conditions) {
|
package/dist/EventsClient.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
|
|
4
|
+
value: true
|
|
5
5
|
});
|
|
6
6
|
|
|
7
7
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
@@ -19,64 +19,97 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
19
19
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
20
20
|
|
|
21
21
|
var EventsClient = function () {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
key: 'evaluateEvent',
|
|
44
|
-
value: function evaluateEvent(featureKey, evaluatedVariant, expectedVariant, user) {
|
|
45
|
-
this.sendEvent('evaluate', 'POST', this.eventsUrl + '/api/sdk/v1/events', [{
|
|
46
|
-
featureKey: featureKey,
|
|
47
|
-
evaluatedVariant: evaluatedVariant,
|
|
48
|
-
expectedVariant: expectedVariant,
|
|
49
|
-
user: user
|
|
50
|
-
}]);
|
|
22
|
+
function EventsClient(apiKey) {
|
|
23
|
+
var _this = this;
|
|
24
|
+
|
|
25
|
+
var eventsUrl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "https://events.featureflow.io";
|
|
26
|
+
var disabled = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
27
|
+
|
|
28
|
+
_classCallCheck(this, EventsClient);
|
|
29
|
+
|
|
30
|
+
this.SEND_INTERVAL = 5;
|
|
31
|
+
this.QUEUE_SIZE = 10000;
|
|
32
|
+
this.clientVersion = 'NodeJsClient/0.6.6';
|
|
33
|
+
this.queue = [];
|
|
34
|
+
this.overLimit = false;
|
|
35
|
+
|
|
36
|
+
this.apiKey = apiKey;
|
|
37
|
+
this.eventsUrl = eventsUrl;
|
|
38
|
+
this.disabled = disabled;
|
|
39
|
+
this.sendInterval = this.SEND_INTERVAL;
|
|
40
|
+
this.interval = setInterval(function () {
|
|
41
|
+
_this.sendQueue();
|
|
42
|
+
}, this.sendInterval * 1000);
|
|
51
43
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
(0, _request2.default)({
|
|
60
|
-
method: method,
|
|
61
|
-
uri: url,
|
|
62
|
-
json: json,
|
|
63
|
-
headers: {
|
|
64
|
-
'Authorization': 'Bearer ' + this.apiKey,
|
|
65
|
-
'X-Featureflow-Client': this.clientVersion
|
|
44
|
+
|
|
45
|
+
_createClass(EventsClient, [{
|
|
46
|
+
key: 'registerFeaturesEvent',
|
|
47
|
+
value: function registerFeaturesEvent(features) {
|
|
48
|
+
this.sendEvent('Register Features', 'PUT', this.eventsUrl + '/api/sdk/v1/register', features);
|
|
66
49
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
50
|
+
}, {
|
|
51
|
+
key: 'evaluateEvent',
|
|
52
|
+
value: function evaluateEvent(featureKey, evaluatedVariant, expectedVariant, user) {
|
|
53
|
+
this.queueEvaluateEvent({
|
|
54
|
+
featureKey: featureKey,
|
|
55
|
+
evaluatedVariant: evaluatedVariant,
|
|
56
|
+
expectedVariant: expectedVariant,
|
|
57
|
+
user: user
|
|
58
|
+
});
|
|
71
59
|
}
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
}, {
|
|
61
|
+
key: 'queueEvaluateEvent',
|
|
62
|
+
value: function queueEvaluateEvent(event) {
|
|
63
|
+
if (this.queue.length < this.QUEUE_SIZE) {
|
|
64
|
+
this.queue.push(event);
|
|
65
|
+
this.overLimit = false;
|
|
66
|
+
} else {
|
|
67
|
+
this.overLimit = true;
|
|
68
|
+
(0, _debug2.default)('Event queue limit exceeded. Increase queue size to prevent dropping events.', method, url, "Event Queue Exceeded");
|
|
69
|
+
}
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
}, {
|
|
72
|
+
key: 'sendQueue',
|
|
73
|
+
value: function sendQueue() {
|
|
74
|
+
if (this.queue.length === 0) return;
|
|
75
|
+
var sendQueue = this.queue;
|
|
76
|
+
this.queue = [];
|
|
77
|
+
|
|
78
|
+
this.sendEvent('evaluate', 'POST', this.eventsUrl + '/api/sdk/v1/events', sendQueue);
|
|
79
|
+
}
|
|
80
|
+
}, {
|
|
81
|
+
key: 'sendEvent',
|
|
82
|
+
value: function sendEvent(eventType, method, url, json) {
|
|
83
|
+
|
|
84
|
+
if (this.disabled) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
(0, _request2.default)({
|
|
88
|
+
method: method,
|
|
89
|
+
uri: url,
|
|
90
|
+
json: json,
|
|
91
|
+
headers: {
|
|
92
|
+
'Authorization': 'Bearer ' + this.apiKey,
|
|
93
|
+
'X-Featureflow-Client': this.clientVersion
|
|
94
|
+
}
|
|
95
|
+
}, function (error, response, body) {
|
|
96
|
+
if (error) {
|
|
97
|
+
(0, _debug2.default)('error %s to "%s". message:', method, url, error.message);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (response.statusCode >= 400) {
|
|
101
|
+
(0, _debug2.default)("unable to send event %s to %s. Failed with response status %d", eventType, url, response.statusCode);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}, {
|
|
106
|
+
key: 'close',
|
|
107
|
+
value: function close() {
|
|
108
|
+
clearInterval(this.interval);
|
|
109
|
+
}
|
|
110
|
+
}]);
|
|
78
111
|
|
|
79
|
-
|
|
112
|
+
return EventsClient;
|
|
80
113
|
}();
|
|
81
114
|
|
|
82
115
|
exports.default = EventsClient;
|
package/dist/FeatureStore.js
CHANGED
|
@@ -38,6 +38,11 @@ var InMemoryFeatureStore = exports.InMemoryFeatureStore = function () {
|
|
|
38
38
|
this.features[key] = _extends({}, features[key]);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
}, {
|
|
42
|
+
key: "getAll",
|
|
43
|
+
value: function getAll() {
|
|
44
|
+
return this.features;
|
|
45
|
+
}
|
|
41
46
|
}]);
|
|
42
47
|
|
|
43
48
|
return InMemoryFeatureStore;
|
package/dist/PollingClient.js
CHANGED
|
@@ -24,22 +24,25 @@ var PollingClient = function () {
|
|
|
24
24
|
|
|
25
25
|
this.DEFAULT_TIMEOUT = 5 * 1000;
|
|
26
26
|
this.DEFAULT_INTERVAL = 10 * 1000;
|
|
27
|
-
this.clientVersion = 'NodeJsClient/0.6.
|
|
27
|
+
this.clientVersion = 'NodeJsClient/0.6.6';
|
|
28
28
|
|
|
29
29
|
this.url = url;
|
|
30
30
|
this.apiKey = config.apiKey;
|
|
31
31
|
this.featureStore = config.featureStore;
|
|
32
|
-
|
|
33
|
-
this.etag = "";
|
|
34
|
-
|
|
35
|
-
this.timeout = this.DEFAULT_TIMEOUT;
|
|
36
32
|
this.interval = this.DEFAULT_INTERVAL;
|
|
33
|
+
if (config.interval && config.interval > 5 || config.interval == 0) {
|
|
34
|
+
this.interval = config.interval * 1000;
|
|
35
|
+
}
|
|
36
|
+
this.timeout = this.DEFAULT_TIMEOUT;
|
|
37
|
+
this.etag = "";
|
|
37
38
|
|
|
38
39
|
this.getFeatures(callback);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if (this.interval > 0) {
|
|
41
|
+
var interval = setInterval(this.getFeatures.bind(this), this.interval);
|
|
42
|
+
return clearInterval.bind(this, interval);
|
|
43
|
+
} else {
|
|
44
|
+
(0, _debug2.default)("Polling interval set to 0. Featureflow will NOT poll for feature changes.");
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
_createClass(PollingClient, [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "featureflow-node-sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Featureflow sdk for Node",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"watch": "babel --watch src -d dist",
|
|
15
15
|
"prepublish": "npm run build",
|
|
16
16
|
"test": "cucumber.js --compiler js:babel-core/register --tags \"not @ignore and not @integration\"",
|
|
17
|
+
"ci-test": "cucumber.js --format json:./cucumber/tests.cucumber --compiler js:babel-core/register --tags \"not @ignore and not @integration\"",
|
|
17
18
|
"test:integration": "cucumber.js --compiler js:babel-core/register --tags \"@integration\""
|
|
18
19
|
},
|
|
19
20
|
"repository": {
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
"sha1-hex": "^1.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"babel-cli": "^6.
|
|
37
|
-
"babel-core": "^6.
|
|
37
|
+
"babel-cli": "^6.26.0",
|
|
38
|
+
"babel-core": "^6.26.0",
|
|
38
39
|
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
|
39
40
|
"babel-plugin-transform-object-rest-spread": "^6.23.0",
|
|
40
41
|
"babel-preset-es2015": "^6.24.0",
|