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 CHANGED
@@ -1,4 +1,10 @@
1
1
  # Change log
2
+ ## [0.6.4] - 2018-012-18
3
+ ### Changed
4
+ - Simplify express middleware
5
+ - Update url to events.featureflow.io for events
6
+ - Improve polling time
7
+ - Update readme
2
8
  ## [0.5.5] - 2017-07-14
3
9
  ### Changed
4
10
  - Updated tests
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [![][npm-img]][npm-url]
4
4
 
5
+ [![Featureflow](https://circleci.com/gh/featureflow/featureflow-node-sdk.svg?style=svg)](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) {
@@ -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
- return a < b;
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
- return a > b;
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: 'ifOff',
34
- value: function ifOff() {
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
  }]);
@@ -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.defaultRule) {
55
+ if (!rule.audience) {
48
56
  return true;
49
57
  } else {
50
58
  for (var cKey in rule.audience.conditions) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
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
- function EventsClient(apiKey) {
23
- var eventsUrl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "https://events.featureflow.io";
24
- var disabled = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
25
-
26
- _classCallCheck(this, EventsClient);
27
-
28
- this.DEFAULT_TIMEOUT = 5 * 1000;
29
- this.clientVersion = 'NodeJsClient/0.6.4';
30
-
31
- this.apiKey = apiKey;
32
- this.eventsUrl = eventsUrl;
33
- this.disabled = disabled;
34
- this.timeout = this.DEFAULT_TIMEOUT;
35
- }
36
-
37
- _createClass(EventsClient, [{
38
- key: 'registerFeaturesEvent',
39
- value: function registerFeaturesEvent(features) {
40
- this.sendEvent('Register Features', 'PUT', this.eventsUrl + '/api/sdk/v1/register', features);
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
- key: 'sendEvent',
54
- value: function sendEvent(eventType, method, url, json) {
55
-
56
- if (this.disabled) {
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
- }, function (error, response, body) {
68
- if (error) {
69
- (0, _debug2.default)('error %s to "%s". message:', method, url, error.message);
70
- return;
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
- if (response.statusCode >= 400) {
73
- (0, _debug2.default)("unable to send event %s to %s. Failed with response status %d", eventType, url, response.statusCode);
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
- return EventsClient;
112
+ return EventsClient;
80
113
  }();
81
114
 
82
115
  exports.default = EventsClient;
@@ -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;
@@ -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.4';
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
- var interval = setInterval(this.getFeatures.bind(this), this.interval);
41
-
42
- return clearInterval.bind(this, interval);
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.4",
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.24.0",
37
- "babel-core": "^6.24.0",
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",