@splitsoftware/splitio-commons 2.4.2-rc.2 → 2.4.2-rc.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.
- package/cjs/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/cjs/storages/AbstractSplitsCacheSync.js +2 -3
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +1 -1
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -3
- package/cjs/storages/inLocalStorage/index.js +4 -1
- package/cjs/storages/inLocalStorage/storageAdapter.js +23 -17
- package/cjs/storages/inLocalStorage/validateCache.js +3 -0
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -0
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -0
- package/esm/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/esm/storages/AbstractSplitsCacheSync.js +2 -3
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +1 -1
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -3
- package/esm/storages/inLocalStorage/index.js +4 -1
- package/esm/storages/inLocalStorage/storageAdapter.js +23 -17
- package/esm/storages/inLocalStorage/validateCache.js +3 -0
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -0
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -0
- package/package.json +1 -1
- package/src/storages/AbstractMySegmentsCacheSync.ts +20 -26
- package/src/storages/AbstractSplitsCacheSync.ts +2 -3
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +1 -1
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +2 -3
- package/src/storages/inLocalStorage/index.ts +6 -2
- package/src/storages/inLocalStorage/storageAdapter.ts +28 -16
- package/src/storages/inLocalStorage/validateCache.ts +3 -0
- package/src/storages/types.ts +3 -1
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -0
- package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -1
- package/types/splitio.d.ts +24 -9
|
@@ -23,45 +23,37 @@ var AbstractMySegmentsCacheSync = /** @class */ (function () {
|
|
|
23
23
|
*/
|
|
24
24
|
AbstractMySegmentsCacheSync.prototype.resetSegments = function (segmentsData) {
|
|
25
25
|
var _this = this;
|
|
26
|
+
this.setChangeNumber(segmentsData.cn);
|
|
26
27
|
var _a = segmentsData, added = _a.added, removed = _a.removed;
|
|
27
|
-
var isDiff = false;
|
|
28
28
|
if (added && removed) {
|
|
29
|
+
var isDiff_1 = false;
|
|
29
30
|
added.forEach(function (segment) {
|
|
30
|
-
|
|
31
|
+
isDiff_1 = _this.addSegment(segment) || isDiff_1;
|
|
31
32
|
});
|
|
32
33
|
removed.forEach(function (segment) {
|
|
33
|
-
|
|
34
|
+
isDiff_1 = _this.removeSegment(segment) || isDiff_1;
|
|
34
35
|
});
|
|
36
|
+
return isDiff_1;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// Slowest path => add and/or remove segments
|
|
53
|
-
for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
54
|
-
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
55
|
-
}
|
|
56
|
-
for (var addIndex = index; addIndex < names.length; addIndex++) {
|
|
57
|
-
this.addSegment(names[addIndex]);
|
|
58
|
-
}
|
|
59
|
-
isDiff = true;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
38
|
+
var names = (segmentsData.k || []).map(function (s) { return s.n; }).sort();
|
|
39
|
+
var storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
40
|
+
// Extreme fast => everything is empty
|
|
41
|
+
if (!names.length && !storedSegmentKeys.length)
|
|
42
|
+
return false;
|
|
43
|
+
var index = 0;
|
|
44
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index])
|
|
45
|
+
index++;
|
|
46
|
+
// Quick path => no changes
|
|
47
|
+
if (index === names.length && index === storedSegmentKeys.length)
|
|
48
|
+
return false;
|
|
49
|
+
// Slowest path => add and/or remove segments
|
|
50
|
+
for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
51
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
62
52
|
}
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
for (var addIndex = index; addIndex < names.length; addIndex++) {
|
|
54
|
+
this.addSegment(names[addIndex]);
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
65
57
|
};
|
|
66
58
|
return AbstractMySegmentsCacheSync;
|
|
67
59
|
}());
|
|
@@ -12,10 +12,9 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
12
12
|
}
|
|
13
13
|
AbstractSplitsCacheSync.prototype.update = function (toAdd, toRemove, changeNumber) {
|
|
14
14
|
var _this = this;
|
|
15
|
-
var updated = toAdd.map(function (addedFF) { return _this.addSplit(addedFF); }).some(function (result) { return result; });
|
|
16
|
-
updated = toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
|
|
17
15
|
this.setChangeNumber(changeNumber);
|
|
18
|
-
return
|
|
16
|
+
var updated = toAdd.map(function (addedFF) { return _this.addSplit(addedFF); }).some(function (result) { return result; });
|
|
17
|
+
return toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
|
|
19
18
|
};
|
|
20
19
|
AbstractSplitsCacheSync.prototype.getSplits = function (names) {
|
|
21
20
|
var _this = this;
|
|
@@ -46,7 +46,7 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
|
|
|
46
46
|
};
|
|
47
47
|
MySegmentsCacheInLocal.prototype.getRegisteredSegments = function () {
|
|
48
48
|
var registeredSegments = [];
|
|
49
|
-
for (var i = 0
|
|
49
|
+
for (var i = 0, len = this.storage.length; i < len; i++) {
|
|
50
50
|
var segmentName = this.keys.extractSegmentName(this.storage.key(i));
|
|
51
51
|
if (segmentName)
|
|
52
52
|
registeredSegments.push(segmentName);
|
|
@@ -18,10 +18,9 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
|
|
|
18
18
|
};
|
|
19
19
|
RBSegmentsCacheInLocal.prototype.update = function (toAdd, toRemove, changeNumber) {
|
|
20
20
|
var _this = this;
|
|
21
|
-
var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
|
|
22
|
-
updated = toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
|
|
23
21
|
this.setChangeNumber(changeNumber);
|
|
24
|
-
return
|
|
22
|
+
var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
|
|
23
|
+
return toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
|
|
25
24
|
};
|
|
26
25
|
RBSegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
27
26
|
try {
|
|
@@ -62,9 +62,12 @@ function InLocalStorage(options) {
|
|
|
62
62
|
validateCache: function () {
|
|
63
63
|
return validateCachePromise || (validateCachePromise = (0, validateCache_1.validateCache)(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
|
|
64
64
|
},
|
|
65
|
-
|
|
65
|
+
save: function () {
|
|
66
66
|
return storage.save && storage.save();
|
|
67
67
|
},
|
|
68
|
+
destroy: function () {
|
|
69
|
+
return storage.whenSaved && storage.whenSaved();
|
|
70
|
+
},
|
|
68
71
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
69
72
|
shared: function (matchingKey) {
|
|
70
73
|
return {
|
|
@@ -2,46 +2,52 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.storageAdapter = void 0;
|
|
4
4
|
var constants_1 = require("./constants");
|
|
5
|
-
function isTillKey(key) {
|
|
6
|
-
return key.endsWith('.till');
|
|
7
|
-
}
|
|
8
5
|
function storageAdapter(log, prefix, wrapper) {
|
|
6
|
+
var keys = [];
|
|
9
7
|
var cache = {};
|
|
10
|
-
var
|
|
11
|
-
var
|
|
8
|
+
var loadPromise;
|
|
9
|
+
var savePromise = Promise.resolve();
|
|
12
10
|
return {
|
|
13
11
|
load: function () {
|
|
14
|
-
return
|
|
12
|
+
return loadPromise || (loadPromise = Promise.resolve().then(function () {
|
|
13
|
+
return wrapper.getItem(prefix);
|
|
14
|
+
}).then(function (storedCache) {
|
|
15
15
|
cache = JSON.parse(storedCache || '{}');
|
|
16
|
+
keys = Object.keys(cache);
|
|
16
17
|
}).catch(function (e) {
|
|
17
|
-
log.error(constants_1.LOG_PREFIX + 'Rejected promise calling
|
|
18
|
+
log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
18
19
|
}));
|
|
19
20
|
},
|
|
20
21
|
save: function () {
|
|
21
|
-
return
|
|
22
|
-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)))
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
return savePromise = savePromise.then(function () {
|
|
23
|
+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
24
|
+
}).catch(function (e) {
|
|
25
|
+
log.error(constants_1.LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
25
26
|
});
|
|
26
27
|
},
|
|
28
|
+
whenSaved: function () {
|
|
29
|
+
return savePromise;
|
|
30
|
+
},
|
|
27
31
|
get length() {
|
|
28
|
-
return
|
|
32
|
+
return keys.length;
|
|
29
33
|
},
|
|
30
34
|
getItem: function (key) {
|
|
31
35
|
return cache[key] || null;
|
|
32
36
|
},
|
|
33
37
|
key: function (index) {
|
|
34
|
-
return
|
|
38
|
+
return keys[index] || null;
|
|
35
39
|
},
|
|
36
40
|
removeItem: function (key) {
|
|
41
|
+
var index = keys.indexOf(key);
|
|
42
|
+
if (index === -1)
|
|
43
|
+
return;
|
|
44
|
+
keys.splice(index, 1);
|
|
37
45
|
delete cache[key];
|
|
38
|
-
if (isTillKey(key))
|
|
39
|
-
this.save();
|
|
40
46
|
},
|
|
41
47
|
setItem: function (key, value) {
|
|
48
|
+
if (keys.indexOf(key) === -1)
|
|
49
|
+
keys.push(key);
|
|
42
50
|
cache[key] = value;
|
|
43
|
-
if (isTillKey(key))
|
|
44
|
-
this.save();
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
53
|
}
|
|
@@ -73,6 +73,9 @@ function validateCache(options, storage, settings, keys, splits, rbSegments, seg
|
|
|
73
73
|
catch (e) {
|
|
74
74
|
settings.log.error(constants_1.LOG_PREFIX + e);
|
|
75
75
|
}
|
|
76
|
+
// Persist clear
|
|
77
|
+
if (storage.save)
|
|
78
|
+
storage.save();
|
|
76
79
|
return false;
|
|
77
80
|
}
|
|
78
81
|
// Check if ready from cache
|
|
@@ -34,6 +34,8 @@ function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmentsEvent
|
|
|
34
34
|
shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
|
|
35
35
|
shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
|
|
36
36
|
}
|
|
37
|
+
if (storage.save)
|
|
38
|
+
storage.save();
|
|
37
39
|
// Notify update if required
|
|
38
40
|
if ((0, AbstractSplitsCacheSync_1.usesSegmentsSync)(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
39
41
|
readyOnAlreadyExistentState = false;
|
|
@@ -154,6 +154,8 @@ function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, splitFilt
|
|
|
154
154
|
segments.registerSegments((0, sets_1.setToArray)(usedSegments))
|
|
155
155
|
]).then(function (_a) {
|
|
156
156
|
var ffChanged = _a[0], rbsChanged = _a[1];
|
|
157
|
+
if (storage.save)
|
|
158
|
+
storage.save();
|
|
157
159
|
if (splitsEventEmitter) {
|
|
158
160
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
159
161
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
|
@@ -20,45 +20,37 @@ var AbstractMySegmentsCacheSync = /** @class */ (function () {
|
|
|
20
20
|
*/
|
|
21
21
|
AbstractMySegmentsCacheSync.prototype.resetSegments = function (segmentsData) {
|
|
22
22
|
var _this = this;
|
|
23
|
+
this.setChangeNumber(segmentsData.cn);
|
|
23
24
|
var _a = segmentsData, added = _a.added, removed = _a.removed;
|
|
24
|
-
var isDiff = false;
|
|
25
25
|
if (added && removed) {
|
|
26
|
+
var isDiff_1 = false;
|
|
26
27
|
added.forEach(function (segment) {
|
|
27
|
-
|
|
28
|
+
isDiff_1 = _this.addSegment(segment) || isDiff_1;
|
|
28
29
|
});
|
|
29
30
|
removed.forEach(function (segment) {
|
|
30
|
-
|
|
31
|
+
isDiff_1 = _this.removeSegment(segment) || isDiff_1;
|
|
31
32
|
});
|
|
33
|
+
return isDiff_1;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
// Slowest path => add and/or remove segments
|
|
50
|
-
for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
51
|
-
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
52
|
-
}
|
|
53
|
-
for (var addIndex = index; addIndex < names.length; addIndex++) {
|
|
54
|
-
this.addSegment(names[addIndex]);
|
|
55
|
-
}
|
|
56
|
-
isDiff = true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
35
|
+
var names = (segmentsData.k || []).map(function (s) { return s.n; }).sort();
|
|
36
|
+
var storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
37
|
+
// Extreme fast => everything is empty
|
|
38
|
+
if (!names.length && !storedSegmentKeys.length)
|
|
39
|
+
return false;
|
|
40
|
+
var index = 0;
|
|
41
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index])
|
|
42
|
+
index++;
|
|
43
|
+
// Quick path => no changes
|
|
44
|
+
if (index === names.length && index === storedSegmentKeys.length)
|
|
45
|
+
return false;
|
|
46
|
+
// Slowest path => add and/or remove segments
|
|
47
|
+
for (var removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
48
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
59
49
|
}
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
for (var addIndex = index; addIndex < names.length; addIndex++) {
|
|
51
|
+
this.addSegment(names[addIndex]);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
62
54
|
};
|
|
63
55
|
return AbstractMySegmentsCacheSync;
|
|
64
56
|
}());
|
|
@@ -9,10 +9,9 @@ var AbstractSplitsCacheSync = /** @class */ (function () {
|
|
|
9
9
|
}
|
|
10
10
|
AbstractSplitsCacheSync.prototype.update = function (toAdd, toRemove, changeNumber) {
|
|
11
11
|
var _this = this;
|
|
12
|
-
var updated = toAdd.map(function (addedFF) { return _this.addSplit(addedFF); }).some(function (result) { return result; });
|
|
13
|
-
updated = toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
|
|
14
12
|
this.setChangeNumber(changeNumber);
|
|
15
|
-
return
|
|
13
|
+
var updated = toAdd.map(function (addedFF) { return _this.addSplit(addedFF); }).some(function (result) { return result; });
|
|
14
|
+
return toRemove.map(function (removedFF) { return _this.removeSplit(removedFF.name); }).some(function (result) { return result; }) || updated;
|
|
16
15
|
};
|
|
17
16
|
AbstractSplitsCacheSync.prototype.getSplits = function (names) {
|
|
18
17
|
var _this = this;
|
|
@@ -43,7 +43,7 @@ var MySegmentsCacheInLocal = /** @class */ (function (_super) {
|
|
|
43
43
|
};
|
|
44
44
|
MySegmentsCacheInLocal.prototype.getRegisteredSegments = function () {
|
|
45
45
|
var registeredSegments = [];
|
|
46
|
-
for (var i = 0
|
|
46
|
+
for (var i = 0, len = this.storage.length; i < len; i++) {
|
|
47
47
|
var segmentName = this.keys.extractSegmentName(this.storage.key(i));
|
|
48
48
|
if (segmentName)
|
|
49
49
|
registeredSegments.push(segmentName);
|
|
@@ -15,10 +15,9 @@ var RBSegmentsCacheInLocal = /** @class */ (function () {
|
|
|
15
15
|
};
|
|
16
16
|
RBSegmentsCacheInLocal.prototype.update = function (toAdd, toRemove, changeNumber) {
|
|
17
17
|
var _this = this;
|
|
18
|
-
var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
|
|
19
|
-
updated = toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
|
|
20
18
|
this.setChangeNumber(changeNumber);
|
|
21
|
-
return
|
|
19
|
+
var updated = toAdd.map(function (toAdd) { return _this.add(toAdd); }).some(function (result) { return result; });
|
|
20
|
+
return toRemove.map(function (toRemove) { return _this.remove(toRemove.name); }).some(function (result) { return result; }) || updated;
|
|
22
21
|
};
|
|
23
22
|
RBSegmentsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
24
23
|
try {
|
|
@@ -59,9 +59,12 @@ export function InLocalStorage(options) {
|
|
|
59
59
|
validateCache: function () {
|
|
60
60
|
return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
|
|
61
61
|
},
|
|
62
|
-
|
|
62
|
+
save: function () {
|
|
63
63
|
return storage.save && storage.save();
|
|
64
64
|
},
|
|
65
|
+
destroy: function () {
|
|
66
|
+
return storage.whenSaved && storage.whenSaved();
|
|
67
|
+
},
|
|
65
68
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
66
69
|
shared: function (matchingKey) {
|
|
67
70
|
return {
|
|
@@ -1,44 +1,50 @@
|
|
|
1
1
|
import { LOG_PREFIX } from './constants';
|
|
2
|
-
function isTillKey(key) {
|
|
3
|
-
return key.endsWith('.till');
|
|
4
|
-
}
|
|
5
2
|
export function storageAdapter(log, prefix, wrapper) {
|
|
3
|
+
var keys = [];
|
|
6
4
|
var cache = {};
|
|
7
|
-
var
|
|
8
|
-
var
|
|
5
|
+
var loadPromise;
|
|
6
|
+
var savePromise = Promise.resolve();
|
|
9
7
|
return {
|
|
10
8
|
load: function () {
|
|
11
|
-
return
|
|
9
|
+
return loadPromise || (loadPromise = Promise.resolve().then(function () {
|
|
10
|
+
return wrapper.getItem(prefix);
|
|
11
|
+
}).then(function (storedCache) {
|
|
12
12
|
cache = JSON.parse(storedCache || '{}');
|
|
13
|
+
keys = Object.keys(cache);
|
|
13
14
|
}).catch(function (e) {
|
|
14
|
-
log.error(LOG_PREFIX + 'Rejected promise calling
|
|
15
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
15
16
|
}));
|
|
16
17
|
},
|
|
17
18
|
save: function () {
|
|
18
|
-
return
|
|
19
|
-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)))
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
return savePromise = savePromise.then(function () {
|
|
20
|
+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
21
|
+
}).catch(function (e) {
|
|
22
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
22
23
|
});
|
|
23
24
|
},
|
|
25
|
+
whenSaved: function () {
|
|
26
|
+
return savePromise;
|
|
27
|
+
},
|
|
24
28
|
get length() {
|
|
25
|
-
return
|
|
29
|
+
return keys.length;
|
|
26
30
|
},
|
|
27
31
|
getItem: function (key) {
|
|
28
32
|
return cache[key] || null;
|
|
29
33
|
},
|
|
30
34
|
key: function (index) {
|
|
31
|
-
return
|
|
35
|
+
return keys[index] || null;
|
|
32
36
|
},
|
|
33
37
|
removeItem: function (key) {
|
|
38
|
+
var index = keys.indexOf(key);
|
|
39
|
+
if (index === -1)
|
|
40
|
+
return;
|
|
41
|
+
keys.splice(index, 1);
|
|
34
42
|
delete cache[key];
|
|
35
|
-
if (isTillKey(key))
|
|
36
|
-
this.save();
|
|
37
43
|
},
|
|
38
44
|
setItem: function (key, value) {
|
|
45
|
+
if (keys.indexOf(key) === -1)
|
|
46
|
+
keys.push(key);
|
|
39
47
|
cache[key] = value;
|
|
40
|
-
if (isTillKey(key))
|
|
41
|
-
this.save();
|
|
42
48
|
}
|
|
43
49
|
};
|
|
44
50
|
}
|
|
@@ -31,6 +31,8 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
|
|
|
31
31
|
shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
|
|
32
32
|
shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
|
|
33
33
|
}
|
|
34
|
+
if (storage.save)
|
|
35
|
+
storage.save();
|
|
34
36
|
// Notify update if required
|
|
35
37
|
if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
36
38
|
readyOnAlreadyExistentState = false;
|
|
@@ -149,6 +149,8 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
|
|
|
149
149
|
segments.registerSegments(setToArray(usedSegments))
|
|
150
150
|
]).then(function (_a) {
|
|
151
151
|
var ffChanged = _a[0], rbsChanged = _a[1];
|
|
152
|
+
if (storage.save)
|
|
153
|
+
storage.save();
|
|
152
154
|
if (splitsEventEmitter) {
|
|
153
155
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
154
156
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
package/package.json
CHANGED
|
@@ -49,10 +49,12 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
49
49
|
* For client-side synchronizer: it resets or updates the cache.
|
|
50
50
|
*/
|
|
51
51
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
52
|
+
this.setChangeNumber(segmentsData.cn);
|
|
53
|
+
|
|
52
54
|
const { added, removed } = segmentsData as MySegmentsData;
|
|
53
|
-
let isDiff = false;
|
|
54
55
|
|
|
55
56
|
if (added && removed) {
|
|
57
|
+
let isDiff = false;
|
|
56
58
|
|
|
57
59
|
added.forEach(segment => {
|
|
58
60
|
isDiff = this.addSegment(segment) || isDiff;
|
|
@@ -61,40 +63,32 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync
|
|
|
61
63
|
removed.forEach(segment => {
|
|
62
64
|
isDiff = this.removeSegment(segment) || isDiff;
|
|
63
65
|
});
|
|
64
|
-
} else {
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
return isDiff;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
isDiff = false;
|
|
72
|
-
} else {
|
|
70
|
+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
|
|
71
|
+
const storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
// Extreme fast => everything is empty
|
|
74
|
+
if (!names.length && !storedSegmentKeys.length) return false;
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
let index = 0;
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
if (index === names.length && index === storedSegmentKeys.length) {
|
|
80
|
-
isDiff = false;
|
|
81
|
-
} else {
|
|
78
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
-
}
|
|
80
|
+
// Quick path => no changes
|
|
81
|
+
if (index === names.length && index === storedSegmentKeys.length) return false;
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
// Slowest path => add and/or remove segments
|
|
84
|
+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
85
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
86
|
+
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
88
|
+
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
89
|
+
this.addSegment(names[addIndex]);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
return isDiff;
|
|
92
|
+
return true;
|
|
99
93
|
}
|
|
100
94
|
}
|
|
@@ -14,10 +14,9 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
14
14
|
protected abstract setChangeNumber(changeNumber: number): boolean | void
|
|
15
15
|
|
|
16
16
|
update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
|
|
17
|
-
let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
18
|
-
updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
19
17
|
this.setChangeNumber(changeNumber);
|
|
20
|
-
|
|
18
|
+
const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
|
|
19
|
+
return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
abstract getSplit(name: string): ISplit | null
|
|
@@ -51,7 +51,7 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
51
51
|
|
|
52
52
|
getRegisteredSegments(): string[] {
|
|
53
53
|
const registeredSegments: string[] = [];
|
|
54
|
-
for (let i = 0
|
|
54
|
+
for (let i = 0, len = this.storage.length; i < len; i++) {
|
|
55
55
|
const segmentName = this.keys.extractSegmentName(this.storage.key(i)!);
|
|
56
56
|
if (segmentName) registeredSegments.push(segmentName);
|
|
57
57
|
}
|
|
@@ -26,10 +26,9 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
29
|
-
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
30
|
-
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
31
29
|
this.setChangeNumber(changeNumber);
|
|
32
|
-
|
|
30
|
+
const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
31
|
+
return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
private setChangeNumber(changeNumber: number) {
|
|
@@ -19,7 +19,7 @@ import { ILogger } from '../../logger/types';
|
|
|
19
19
|
import SplitIO from '../../../types/splitio';
|
|
20
20
|
import { storageAdapter } from './storageAdapter';
|
|
21
21
|
|
|
22
|
-
function validateStorage(log: ILogger, prefix: string, wrapper?: SplitIO.
|
|
22
|
+
function validateStorage(log: ILogger, prefix: string, wrapper?: SplitIO.SyncStorageWrapper | SplitIO.AsyncStorageWrapper): StorageAdapter | undefined {
|
|
23
23
|
if (wrapper) {
|
|
24
24
|
if (isValidStorageWrapper(wrapper)) {
|
|
25
25
|
return isWebStorage(wrapper) ?
|
|
@@ -71,10 +71,14 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
71
71
|
return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
|
|
72
72
|
},
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
save() {
|
|
75
75
|
return storage.save && storage.save();
|
|
76
76
|
},
|
|
77
77
|
|
|
78
|
+
destroy() {
|
|
79
|
+
return storage.whenSaved && storage.whenSaved();
|
|
80
|
+
},
|
|
81
|
+
|
|
78
82
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
79
83
|
shared(matchingKey: string) {
|
|
80
84
|
|
|
@@ -3,48 +3,60 @@ import SplitIO from '../../../types/splitio';
|
|
|
3
3
|
import { LOG_PREFIX } from './constants';
|
|
4
4
|
import { StorageAdapter } from '../types';
|
|
5
5
|
|
|
6
|
-
function isTillKey(key: string) {
|
|
7
|
-
return key.endsWith('.till');
|
|
8
|
-
}
|
|
9
6
|
|
|
10
|
-
export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.
|
|
7
|
+
export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.SyncStorageWrapper | SplitIO.AsyncStorageWrapper): Required<StorageAdapter> {
|
|
8
|
+
let keys: string[] = [];
|
|
11
9
|
let cache: Record<string, string> = {};
|
|
12
10
|
|
|
13
|
-
let
|
|
14
|
-
let
|
|
11
|
+
let loadPromise: Promise<void> | undefined;
|
|
12
|
+
let savePromise = Promise.resolve();
|
|
15
13
|
|
|
16
14
|
return {
|
|
17
15
|
load() {
|
|
18
|
-
return
|
|
16
|
+
return loadPromise || (loadPromise = Promise.resolve().then(() => {
|
|
17
|
+
return wrapper.getItem(prefix);
|
|
18
|
+
}).then((storedCache) => {
|
|
19
19
|
cache = JSON.parse(storedCache || '{}');
|
|
20
|
+
keys = Object.keys(cache);
|
|
20
21
|
}).catch((e) => {
|
|
21
|
-
log.error(LOG_PREFIX + 'Rejected promise calling
|
|
22
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
22
23
|
}));
|
|
23
24
|
},
|
|
25
|
+
|
|
24
26
|
save() {
|
|
25
|
-
return
|
|
26
|
-
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)))
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
return savePromise = savePromise.then(() => {
|
|
28
|
+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
29
|
+
}).catch((e) => {
|
|
30
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
29
31
|
});
|
|
30
32
|
},
|
|
31
33
|
|
|
34
|
+
whenSaved() {
|
|
35
|
+
return savePromise;
|
|
36
|
+
},
|
|
37
|
+
|
|
32
38
|
get length() {
|
|
33
|
-
return
|
|
39
|
+
return keys.length;
|
|
34
40
|
},
|
|
41
|
+
|
|
35
42
|
getItem(key: string) {
|
|
36
43
|
return cache[key] || null;
|
|
37
44
|
},
|
|
45
|
+
|
|
38
46
|
key(index: number) {
|
|
39
|
-
return
|
|
47
|
+
return keys[index] || null;
|
|
40
48
|
},
|
|
49
|
+
|
|
41
50
|
removeItem(key: string) {
|
|
51
|
+
const index = keys.indexOf(key);
|
|
52
|
+
if (index === -1) return;
|
|
53
|
+
keys.splice(index, 1);
|
|
42
54
|
delete cache[key];
|
|
43
|
-
if (isTillKey(key)) this.save!();
|
|
44
55
|
},
|
|
56
|
+
|
|
45
57
|
setItem(key: string, value: string) {
|
|
58
|
+
if (keys.indexOf(key) === -1) keys.push(key);
|
|
46
59
|
cache[key] = value;
|
|
47
|
-
if (isTillKey(key)) this.save!();
|
|
48
60
|
}
|
|
49
61
|
};
|
|
50
62
|
}
|
package/src/storages/types.ts
CHANGED
|
@@ -12,10 +12,11 @@ export interface StorageAdapter {
|
|
|
12
12
|
// Methods to support async storages
|
|
13
13
|
load?: () => Promise<void>;
|
|
14
14
|
save?: () => Promise<void>;
|
|
15
|
+
whenSaved?: () => Promise<void>;
|
|
15
16
|
// Methods based on https://developer.mozilla.org/en-US/docs/Web/API/Storage
|
|
16
17
|
readonly length: number;
|
|
17
|
-
getItem(key: string): string | null;
|
|
18
18
|
key(index: number): string | null;
|
|
19
|
+
getItem(key: string): string | null;
|
|
19
20
|
removeItem(key: string): void;
|
|
20
21
|
setItem(key: string, value: string): void;
|
|
21
22
|
}
|
|
@@ -482,6 +483,7 @@ export interface IStorageBase<
|
|
|
482
483
|
uniqueKeys: TUniqueKeysCache,
|
|
483
484
|
destroy(): void | Promise<void>,
|
|
484
485
|
shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
|
|
486
|
+
save?: () => void | Promise<void>,
|
|
485
487
|
}
|
|
486
488
|
|
|
487
489
|
export interface IStorageSync extends IStorageBase<
|
|
@@ -51,6 +51,8 @@ export function mySegmentsUpdaterFactory(
|
|
|
51
51
|
shouldNotifyUpdate = largeSegments!.resetSegments((segmentsData as IMembershipsResponse).ls || {}) || shouldNotifyUpdate;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (storage.save) storage.save();
|
|
55
|
+
|
|
54
56
|
// Notify update if required
|
|
55
57
|
if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
56
58
|
readyOnAlreadyExistentState = false;
|
|
@@ -117,7 +117,7 @@ export function computeMutation<T extends ISplit | IRBSegment>(rules: Array<T>,
|
|
|
117
117
|
export function splitChangesUpdaterFactory(
|
|
118
118
|
log: ILogger,
|
|
119
119
|
splitChangesFetcher: ISplitChangesFetcher,
|
|
120
|
-
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments'>,
|
|
120
|
+
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments' | 'save'>,
|
|
121
121
|
splitFiltersValidation: ISplitFiltersValidation,
|
|
122
122
|
splitsEventEmitter?: ISplitsEventEmitter,
|
|
123
123
|
requestTimeoutBeforeReady: number = 0,
|
|
@@ -185,6 +185,8 @@ export function splitChangesUpdaterFactory(
|
|
|
185
185
|
// @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
|
|
186
186
|
segments.registerSegments(setToArray(usedSegments))
|
|
187
187
|
]).then(([ffChanged, rbsChanged]) => {
|
|
188
|
+
if (storage.save) storage.save();
|
|
189
|
+
|
|
188
190
|
if (splitsEventEmitter) {
|
|
189
191
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
190
192
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
package/types/splitio.d.ts
CHANGED
|
@@ -458,19 +458,34 @@ interface IClientSideSyncSharedSettings extends IClientSideSharedSettings, ISync
|
|
|
458
458
|
*/
|
|
459
459
|
declare namespace SplitIO {
|
|
460
460
|
|
|
461
|
-
interface
|
|
461
|
+
interface SyncStorageWrapper {
|
|
462
462
|
/**
|
|
463
|
-
* Returns
|
|
463
|
+
* Returns the value associated with the given key, or null if the key does not exist.
|
|
464
464
|
*/
|
|
465
|
-
getItem(key: string):
|
|
465
|
+
getItem(key: string): string | null;
|
|
466
466
|
/**
|
|
467
|
-
*
|
|
467
|
+
* Sets the value for the given key, creating a new key/value pair if key does not exist.
|
|
468
468
|
*/
|
|
469
|
-
setItem(key: string, value: string):
|
|
469
|
+
setItem(key: string, value: string): void;
|
|
470
470
|
/**
|
|
471
|
-
*
|
|
471
|
+
* Removes the key/value pair for the given key, if the key exists.
|
|
472
472
|
*/
|
|
473
|
-
removeItem(key: string):
|
|
473
|
+
removeItem(key: string): void;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
interface AsyncStorageWrapper {
|
|
477
|
+
/**
|
|
478
|
+
* Returns a promise that resolves to the value associated with the given key, or null if the key does not exist.
|
|
479
|
+
*/
|
|
480
|
+
getItem(key: string): Promise<string | null>;
|
|
481
|
+
/**
|
|
482
|
+
* Returns a promise that resolves when the value of the pair identified by key is set to value, creating a new key/value pair if key does not exist.
|
|
483
|
+
*/
|
|
484
|
+
setItem(key: string, value: string): Promise<void>;
|
|
485
|
+
/**
|
|
486
|
+
* Returns a promise that resolves when the key/value pair for the given key is removed, if the key exists.
|
|
487
|
+
*/
|
|
488
|
+
removeItem(key: string): Promise<void>;
|
|
474
489
|
}
|
|
475
490
|
|
|
476
491
|
/**
|
|
@@ -992,7 +1007,7 @@ declare namespace SplitIO {
|
|
|
992
1007
|
*
|
|
993
1008
|
* @defaultValue `window.localStorage`
|
|
994
1009
|
*/
|
|
995
|
-
wrapper?:
|
|
1010
|
+
wrapper?: SyncStorageWrapper | AsyncStorageWrapper;
|
|
996
1011
|
}
|
|
997
1012
|
/**
|
|
998
1013
|
* Storage for asynchronous (consumer) SDK.
|
|
@@ -1338,7 +1353,7 @@ declare namespace SplitIO {
|
|
|
1338
1353
|
*
|
|
1339
1354
|
* @defaultValue `window.localStorage`
|
|
1340
1355
|
*/
|
|
1341
|
-
wrapper?:
|
|
1356
|
+
wrapper?: SyncStorageWrapper | AsyncStorageWrapper;
|
|
1342
1357
|
};
|
|
1343
1358
|
}
|
|
1344
1359
|
/**
|