@splitsoftware/splitio-commons 2.0.0-rc.0 → 2.0.0-rc.2

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 (114) hide show
  1. package/CHANGES.txt +5 -1
  2. package/cjs/evaluator/Engine.js +1 -1
  3. package/cjs/evaluator/index.js +1 -1
  4. package/cjs/readiness/readinessManager.js +13 -2
  5. package/cjs/sdkClient/sdkClientMethodCS.js +0 -1
  6. package/cjs/sdkFactory/index.js +29 -9
  7. package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
  8. package/cjs/storages/dataLoader.js +99 -37
  9. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  10. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
  11. package/cjs/storages/inLocalStorage/index.js +1 -1
  12. package/cjs/storages/inMemory/InMemoryStorageCS.js +18 -6
  13. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  14. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  15. package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
  16. package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
  17. package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
  18. package/cjs/storages/inRedis/RedisAdapter.js +2 -1
  19. package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  20. package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
  21. package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
  22. package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
  23. package/cjs/storages/pluggable/inMemoryWrapper.js +2 -1
  24. package/cjs/sync/offline/syncManagerOffline.js +18 -11
  25. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  26. package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -1
  27. package/cjs/sync/syncManagerOnline.js +20 -21
  28. package/cjs/trackers/eventTracker.js +12 -10
  29. package/cjs/trackers/impressionsTracker.js +16 -14
  30. package/cjs/trackers/uniqueKeysTracker.js +5 -3
  31. package/cjs/utils/lang/sets.js +12 -2
  32. package/esm/evaluator/Engine.js +1 -1
  33. package/esm/evaluator/index.js +2 -2
  34. package/esm/readiness/readinessManager.js +13 -2
  35. package/esm/sdkClient/sdkClientMethodCS.js +0 -1
  36. package/esm/sdkFactory/index.js +30 -10
  37. package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
  38. package/esm/storages/dataLoader.js +96 -35
  39. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  40. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
  41. package/esm/storages/inLocalStorage/index.js +1 -1
  42. package/esm/storages/inMemory/InMemoryStorageCS.js +18 -6
  43. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  44. package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  45. package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
  46. package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
  47. package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
  48. package/esm/storages/inRedis/RedisAdapter.js +2 -1
  49. package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  50. package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
  51. package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
  52. package/esm/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
  53. package/esm/storages/pluggable/inMemoryWrapper.js +2 -1
  54. package/esm/sync/offline/syncManagerOffline.js +18 -11
  55. package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  56. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -1
  57. package/esm/sync/syncManagerOnline.js +20 -21
  58. package/esm/trackers/eventTracker.js +12 -10
  59. package/esm/trackers/impressionsTracker.js +16 -14
  60. package/esm/trackers/uniqueKeysTracker.js +5 -3
  61. package/esm/utils/lang/sets.js +10 -1
  62. package/package.json +1 -1
  63. package/src/evaluator/Engine.ts +1 -1
  64. package/src/evaluator/index.ts +2 -2
  65. package/src/readiness/readinessManager.ts +12 -3
  66. package/src/readiness/types.ts +3 -0
  67. package/src/sdkClient/sdkClientMethodCS.ts +0 -2
  68. package/src/sdkFactory/index.ts +34 -12
  69. package/src/sdkFactory/types.ts +2 -0
  70. package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
  71. package/src/storages/dataLoader.ts +97 -38
  72. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
  73. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +3 -2
  74. package/src/storages/inLocalStorage/index.ts +1 -1
  75. package/src/storages/inMemory/InMemoryStorageCS.ts +21 -6
  76. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
  77. package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
  78. package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
  79. package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +2 -1
  80. package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +2 -1
  81. package/src/storages/inRedis/RedisAdapter.ts +2 -1
  82. package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
  83. package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +2 -1
  84. package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
  85. package/src/storages/pluggable/UniqueKeysCachePluggable.ts +2 -1
  86. package/src/storages/pluggable/inMemoryWrapper.ts +2 -1
  87. package/src/storages/types.ts +7 -11
  88. package/src/sync/offline/syncManagerOffline.ts +21 -13
  89. package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
  90. package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -1
  91. package/src/sync/syncManagerOnline.ts +17 -17
  92. package/src/sync/types.ts +1 -1
  93. package/src/trackers/eventTracker.ts +11 -8
  94. package/src/trackers/impressionsTracker.ts +13 -10
  95. package/src/trackers/types.ts +1 -0
  96. package/src/trackers/uniqueKeysTracker.ts +6 -4
  97. package/src/types.ts +14 -13
  98. package/src/utils/lang/sets.ts +11 -1
  99. package/types/readiness/types.d.ts +3 -0
  100. package/types/sdkFactory/types.d.ts +1 -0
  101. package/types/storages/dataLoader.d.ts +17 -6
  102. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
  103. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
  104. package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
  105. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
  106. package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
  107. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
  108. package/types/storages/types.d.ts +7 -10
  109. package/types/sync/types.d.ts +1 -1
  110. package/types/trackers/eventTracker.d.ts +1 -1
  111. package/types/trackers/impressionsTracker.d.ts +1 -1
  112. package/types/trackers/types.d.ts +1 -0
  113. package/types/types.d.ts +13 -13
  114. package/types/utils/lang/sets.d.ts +1 -0
@@ -2,6 +2,7 @@ import { __extends } from "tslib";
2
2
  import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
3
3
  import { DEFAULT_CACHE_SIZE, REFRESH_RATE, TTL_REFRESH } from './constants';
4
4
  import { LOG_PREFIX } from './constants';
5
+ import { setToArray } from '../../utils/lang/sets';
5
6
  var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
6
7
  __extends(UniqueKeysCacheInRedis, _super);
7
8
  function UniqueKeysCacheInRedis(log, key, redis, uniqueKeysQueueSize, refreshRate) {
@@ -21,7 +22,7 @@ var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
21
22
  if (!featureNames.length)
22
23
  return Promise.resolve(false);
23
24
  var uniqueKeysArray = featureNames.map(function (featureName) {
24
- var featureKeys = Array.from(_this.uniqueKeysTracker[featureName]);
25
+ var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
25
26
  var uniqueKeysPayload = {
26
27
  f: featureName,
27
28
  ks: featureKeys
@@ -12,32 +12,19 @@ var SegmentsCachePluggable = /** @class */ (function () {
12
12
  this.wrapper = wrapper;
13
13
  }
14
14
  /**
15
- * Add a list of `segmentKeys` to the given segment `name`.
16
- * The returned promise is resolved when the operation success
17
- * or rejected if wrapper operation fails.
18
- */
19
- SegmentsCachePluggable.prototype.addToSegment = function (name, segmentKeys) {
20
- var segmentKey = this.keys.buildSegmentNameKey(name);
21
- if (segmentKeys.length) {
22
- return this.wrapper.addItems(segmentKey, segmentKeys);
23
- }
24
- else {
25
- return Promise.resolve();
26
- }
27
- };
28
- /**
29
- * Remove a list of `segmentKeys` from the given segment `name`.
30
- * The returned promise is resolved when the operation success
31
- * or rejected if wrapper operation fails.
15
+ * Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
16
+ * The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
17
+ * or rejected if it fails (e.g., wrapper operation fails).
32
18
  */
33
- SegmentsCachePluggable.prototype.removeFromSegment = function (name, segmentKeys) {
19
+ SegmentsCachePluggable.prototype.update = function (name, addedKeys, removedKeys, changeNumber) {
34
20
  var segmentKey = this.keys.buildSegmentNameKey(name);
35
- if (segmentKeys.length) {
36
- return this.wrapper.removeItems(segmentKey, segmentKeys);
37
- }
38
- else {
39
- return Promise.resolve();
40
- }
21
+ return Promise.all([
22
+ addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
23
+ removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
24
+ this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
25
+ ]).then(function () {
26
+ return addedKeys.length > 0 || removedKeys.length > 0;
27
+ });
41
28
  };
42
29
  /**
43
30
  * Returns a promise that resolves with a boolean value indicating if `key` is part of `name` segment.
@@ -46,14 +33,6 @@ var SegmentsCachePluggable = /** @class */ (function () {
46
33
  SegmentsCachePluggable.prototype.isInSegment = function (name, key) {
47
34
  return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
48
35
  };
49
- /**
50
- * Set till number for the given segment `name`.
51
- * The returned promise is resolved when the operation success,
52
- * or rejected if it fails (e.g., wrapper operation fails).
53
- */
54
- SegmentsCachePluggable.prototype.setChangeNumber = function (name, changeNumber) {
55
- return this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '');
56
- };
57
36
  /**
58
37
  * Get till number or -1 if it's not defined.
59
38
  * The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
@@ -2,6 +2,7 @@ import { __extends } from "tslib";
2
2
  import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
3
3
  import { DEFAULT_CACHE_SIZE, REFRESH_RATE } from '../inRedis/constants';
4
4
  import { LOG_PREFIX } from './constants';
5
+ import { setToArray } from '../../utils/lang/sets';
5
6
  var UniqueKeysCachePluggable = /** @class */ (function (_super) {
6
7
  __extends(UniqueKeysCachePluggable, _super);
7
8
  function UniqueKeysCachePluggable(log, key, wrapper, uniqueKeysQueueSize, refreshRate) {
@@ -21,7 +22,7 @@ var UniqueKeysCachePluggable = /** @class */ (function (_super) {
21
22
  if (!featureNames.length)
22
23
  return Promise.resolve(false);
23
24
  var uniqueKeysArray = featureNames.map(function (featureName) {
24
- var featureKeys = Array.from(_this.uniqueKeysTracker[featureName]);
25
+ var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
25
26
  var uniqueKeysPayload = {
26
27
  f: featureName,
27
28
  ks: featureKeys
@@ -1,4 +1,5 @@
1
1
  import { startsWith, toNumber } from '../../utils/lang';
2
+ import { setToArray } from '../../utils/lang/sets';
2
3
  /**
3
4
  * Creates a IPluggableStorageWrapper implementation that stores items in memory.
4
5
  * The `_cache` property is the object were items are stored.
@@ -115,7 +116,7 @@ export function inMemoryWrapperFactory(connDelay) {
115
116
  if (!set)
116
117
  return Promise.resolve([]);
117
118
  if (set instanceof Set)
118
- return Promise.resolve(Array.from(set));
119
+ return Promise.resolve(setToArray(set));
119
120
  return Promise.reject('key is not a set');
120
121
  },
121
122
  // always connects and disconnects
@@ -16,23 +16,30 @@ export function syncManagerOfflineFactory(splitsParserFactory) {
16
16
  */
17
17
  return function (_a) {
18
18
  var settings = _a.settings, readiness = _a.readiness, storage = _a.storage;
19
- return objectAssign(fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings), {
19
+ var mainSyncManager = fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings);
20
+ var mainStart = mainSyncManager.start;
21
+ var sharedStarts = [];
22
+ return objectAssign(mainSyncManager, {
23
+ start: function () {
24
+ mainStart();
25
+ sharedStarts.forEach(function (cb) { return cb(); });
26
+ sharedStarts.length = 0;
27
+ },
20
28
  // fake flush, that resolves immediately
21
29
  flush: flush,
22
30
  // [Only used for client-side]
23
31
  shared: function (matchingKey, readinessManager) {
32
+ // In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
33
+ // SDK_READY cannot be emitted directly because this will not update the readiness status
34
+ function emitSdkReady() {
35
+ readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
36
+ }
37
+ if (mainSyncManager.isRunning())
38
+ setTimeout(emitSdkReady);
39
+ else
40
+ sharedStarts.push(emitSdkReady);
24
41
  return {
25
- start: function () {
26
- // In LOCALHOST mode, shared clients are ready in the next event-loop cycle than created
27
- // SDK_READY cannot be emitted directly because this will not update the readiness status
28
- setTimeout(function () {
29
- readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED); // SDK_SPLITS_ARRIVED emitted by main SyncManager
30
- }, 0);
31
- },
32
42
  stop: function () { },
33
- isRunning: function () {
34
- return true;
35
- },
36
43
  flush: flush,
37
44
  };
38
45
  }
@@ -1,7 +1,5 @@
1
- import { findIndex } from '../../../utils/lang';
2
1
  import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
3
2
  import { LOG_PREFIX_INSTANTIATION, LOG_PREFIX_SYNC_SEGMENTS } from '../../../logger/constants';
4
- import { thenable } from '../../../utils/promise/thenable';
5
3
  /**
6
4
  * Factory of SegmentChanges updater, a task that:
7
5
  * - fetches segment changes using `segmentChangesFetcher`
@@ -20,27 +18,16 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
20
18
  var sincePromise = Promise.resolve(segments.getChangeNumber(segmentName));
21
19
  return sincePromise.then(function (since) {
22
20
  // if fetchOnlyNew flag, avoid processing already fetched segments
23
- if (fetchOnlyNew && since !== -1)
24
- return -1;
25
- return segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
26
- var changeNumber = -1;
27
- var results = [];
28
- changes.forEach(function (x) {
29
- if (x.added.length > 0)
30
- results.push(segments.addToSegment(segmentName, x.added));
31
- if (x.removed.length > 0)
32
- results.push(segments.removeFromSegment(segmentName, x.removed));
33
- if (x.added.length > 0 || x.removed.length > 0) {
34
- results.push(segments.setChangeNumber(segmentName, x.till));
35
- changeNumber = x.till;
36
- }
37
- log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processed " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
21
+ return fetchOnlyNew && since !== -1 ?
22
+ false :
23
+ segmentChangesFetcher(since, segmentName, noCache, till).then(function (changes) {
24
+ return Promise.all(changes.map(function (x) {
25
+ log.debug(LOG_PREFIX_SYNC_SEGMENTS + "Processing " + segmentName + " with till = " + x.till + ". Added: " + x.added.length + ". Removed: " + x.removed.length);
26
+ return segments.update(segmentName, x.added, x.removed, x.till);
27
+ })).then(function (updates) {
28
+ return updates.some(function (update) { return update; });
29
+ });
38
30
  });
39
- // If at least one storage operation result is a promise, join all in a single promise.
40
- if (results.some(function (result) { return thenable(result); }))
41
- return Promise.all(results).then(function () { return changeNumber; });
42
- return changeNumber;
43
- });
44
31
  });
45
32
  }
46
33
  /**
@@ -59,14 +46,11 @@ export function segmentChangesUpdaterFactory(log, segmentChangesFetcher, segment
59
46
  // If not a segment name provided, read list of available segments names to be updated.
60
47
  var segmentsPromise = Promise.resolve(segmentName ? [segmentName] : segments.getRegisteredSegments());
61
48
  return segmentsPromise.then(function (segmentNames) {
62
- // Async fetchers are collected here.
63
- var updaters = [];
64
- for (var index = 0; index < segmentNames.length; index++) {
65
- updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));
66
- }
49
+ // Async fetchers
50
+ var updaters = segmentNames.map(function (segmentName) { return updateSegment(segmentName, noCache, till, fetchOnlyNew); });
67
51
  return Promise.all(updaters).then(function (shouldUpdateFlags) {
68
52
  // if at least one segment fetch succeeded, mark segments ready
69
- if (findIndex(shouldUpdateFlags, function (v) { return v !== -1; }) !== -1 || readyOnAlreadyExistentState) {
53
+ if (shouldUpdateFlags.some(function (update) { return update; }) || readyOnAlreadyExistentState) {
70
54
  readyOnAlreadyExistentState = false;
71
55
  if (readiness)
72
56
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
@@ -3,6 +3,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/
3
3
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
4
4
  import { startsWith } from '../../../utils/lang';
5
5
  import { IN_SEGMENT } from '../../../utils/constants';
6
+ import { setToArray } from '../../../utils/lang/sets';
6
7
  // Checks that all registered segments have been fetched (changeNumber !== -1 for every segment).
7
8
  // Returns a promise that could be rejected.
8
9
  // @TODO review together with Segments and MySegments storage APIs
@@ -67,7 +68,7 @@ export function computeSplitsMutation(entries, filters) {
67
68
  }
68
69
  return accum;
69
70
  }, { added: [], removed: [], segments: [] });
70
- computed.segments = Array.from(segments);
71
+ computed.segments = setToArray(segments);
71
72
  return computed;
72
73
  }
73
74
  /**
@@ -115,33 +115,32 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
115
115
  if (!pollingManager)
116
116
  return;
117
117
  var mySegmentsSyncTask = pollingManager.add(matchingKey, readinessManager, storage);
118
- return {
119
- isRunning: mySegmentsSyncTask.isRunning,
120
- start: function () {
121
- if (syncEnabled) {
122
- if (pushManager) {
123
- if (pollingManager.isRunning()) {
124
- // if doing polling, we must start the periodic fetch of data
125
- if (storage.splits.usesSegments())
126
- mySegmentsSyncTask.start();
127
- }
128
- else {
129
- // if not polling, we must execute the sync task for the initial fetch
130
- // of segments since `syncAll` was already executed when starting the main client
131
- mySegmentsSyncTask.execute();
132
- }
133
- pushManager.add(matchingKey, mySegmentsSyncTask);
134
- }
135
- else {
118
+ if (running) {
119
+ if (syncEnabled) {
120
+ if (pushManager) {
121
+ if (pollingManager.isRunning()) {
122
+ // if doing polling, we must start the periodic fetch of data
136
123
  if (storage.splits.usesSegments())
137
124
  mySegmentsSyncTask.start();
138
125
  }
126
+ else {
127
+ // if not polling, we must execute the sync task for the initial fetch
128
+ // of segments since `syncAll` was already executed when starting the main client
129
+ mySegmentsSyncTask.execute();
130
+ }
131
+ pushManager.add(matchingKey, mySegmentsSyncTask);
139
132
  }
140
133
  else {
141
- if (!readinessManager.isReady())
142
- mySegmentsSyncTask.execute();
134
+ if (storage.splits.usesSegments())
135
+ mySegmentsSyncTask.start();
143
136
  }
144
- },
137
+ }
138
+ else {
139
+ if (!readinessManager.isReady())
140
+ mySegmentsSyncTask.execute();
141
+ }
142
+ }
143
+ return {
145
144
  stop: function () {
146
145
  // check in case `client.destroy()` has been invoked more than once for the same client
147
146
  var mySegmentsSyncTask = pollingManager.get(matchingKey);
@@ -9,7 +9,7 @@ import { isConsumerMode } from '../utils/settingsValidation/mode';
9
9
  * @param eventsCache cache to save events
10
10
  * @param integrationsManager optional event handler used for integrations
11
11
  */
12
- export function eventTrackerFactory(settings, eventsCache, integrationsManager, telemetryCache) {
12
+ export function eventTrackerFactory(settings, eventsCache, whenInit, integrationsManager, telemetryCache) {
13
13
  var log = settings.log, mode = settings.mode;
14
14
  var isAsync = isConsumerMode(mode);
15
15
  function queueEventsCallback(eventData, tracked) {
@@ -19,15 +19,17 @@ export function eventTrackerFactory(settings, eventsCache, integrationsManager,
19
19
  if (tracked) {
20
20
  log.info(EVENTS_TRACKER_SUCCESS, [msg]);
21
21
  if (integrationsManager) {
22
- // Wrap in a timeout because we don't want it to be blocking.
23
- setTimeout(function () {
24
- // copy of event, to avoid unexpected behaviour if modified by integrations
25
- var eventDataCopy = objectAssign({}, eventData);
26
- if (properties)
27
- eventDataCopy.properties = objectAssign({}, properties);
28
- // integrationsManager does not throw errors (they are internally handled by each integration module)
29
- integrationsManager.handleEvent(eventDataCopy);
30
- }, 0);
22
+ whenInit(function () {
23
+ // Wrap in a timeout because we don't want it to be blocking.
24
+ setTimeout(function () {
25
+ // copy of event, to avoid unexpected behaviour if modified by integrations
26
+ var eventDataCopy = objectAssign({}, eventData);
27
+ if (properties)
28
+ eventDataCopy.properties = objectAssign({}, properties);
29
+ // integrationsManager does not throw errors (they are internally handled by each integration module)
30
+ integrationsManager.handleEvent(eventDataCopy);
31
+ });
32
+ });
31
33
  }
32
34
  }
33
35
  else {
@@ -11,7 +11,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
11
11
  * @param integrationsManager optional integrations manager
12
12
  * @param strategy strategy for impressions tracking.
13
13
  */
14
- export function impressionsTrackerFactory(settings, impressionsCache, strategy, integrationsManager, telemetryCache) {
14
+ export function impressionsTrackerFactory(settings, impressionsCache, strategy, whenInit, integrationsManager, telemetryCache) {
15
15
  var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
16
16
  return {
17
17
  track: function (impressions, attributes) {
@@ -50,19 +50,21 @@ export function impressionsTrackerFactory(settings, impressionsCache, strategy,
50
50
  hostname: hostname,
51
51
  sdkLanguageVersion: version
52
52
  };
53
- // Wrap in a timeout because we don't want it to be blocking.
54
- setTimeout(function () {
55
- // integrationsManager.handleImpression does not throw errors
56
- if (integrationsManager)
57
- integrationsManager.handleImpression(impressionData);
58
- try { // @ts-ignore. An exception on the listeners should not break the SDK.
59
- if (impressionListener)
60
- impressionListener.logImpression(impressionData);
61
- }
62
- catch (err) {
63
- log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
64
- }
65
- }, 0);
53
+ whenInit(function () {
54
+ // Wrap in a timeout because we don't want it to be blocking.
55
+ setTimeout(function () {
56
+ // integrationsManager.handleImpression does not throw errors
57
+ if (integrationsManager)
58
+ integrationsManager.handleImpression(impressionData);
59
+ try { // @ts-ignore. An exception on the listeners should not break the SDK.
60
+ if (impressionListener)
61
+ impressionListener.logImpression(impressionData);
62
+ }
63
+ catch (err) {
64
+ log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
65
+ }
66
+ });
67
+ });
66
68
  };
67
69
  for (var i = 0; i < impressionsToListenerCount; i++) {
68
70
  _loop_1(i);
@@ -16,9 +16,6 @@ var noopFilterAdapter = {
16
16
  export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
17
17
  if (filterAdapter === void 0) { filterAdapter = noopFilterAdapter; }
18
18
  var intervalId;
19
- if (filterAdapter.refreshRate) {
20
- intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
21
- }
22
19
  return {
23
20
  track: function (key, featureName) {
24
21
  if (!filterAdapter.add(key, featureName)) {
@@ -27,6 +24,11 @@ export function uniqueKeysTrackerFactory(log, uniqueKeysCache, filterAdapter) {
27
24
  }
28
25
  uniqueKeysCache.track(key, featureName);
29
26
  },
27
+ start: function () {
28
+ if (filterAdapter.refreshRate) {
29
+ intervalId = setInterval(filterAdapter.clear, filterAdapter.refreshRate);
30
+ }
31
+ },
30
32
  stop: function () {
31
33
  clearInterval(intervalId);
32
34
  }
@@ -1,5 +1,14 @@
1
+ export function setToArray(set) {
2
+ if (Array.from)
3
+ return Array.from(set);
4
+ var array = [];
5
+ set.forEach(function (value) {
6
+ array.push(value);
7
+ });
8
+ return array;
9
+ }
1
10
  export function returnSetsUnion(set, set2) {
2
- return new Set(Array.from(set).concat(Array.from(set2)));
11
+ return new Set(setToArray(set).concat(setToArray(set2)));
3
12
  }
4
13
  export function returnDifference(list, list2) {
5
14
  if (list === void 0) { list = []; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "2.0.0-rc.0",
3
+ "version": "2.0.0-rc.2",
4
4
  "description": "Split JavaScript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -21,7 +21,7 @@ export class Engine {
21
21
 
22
22
  constructor(private baseInfo: ISplit, private evaluator: IEvaluator) {
23
23
 
24
- // in case we don't have a default treatment in the instanciation, use 'control'
24
+ // in case we don't have a default treatment in the instantiation, use 'control'
25
25
  if (typeof this.baseInfo.defaultTreatment !== 'string') {
26
26
  this.baseInfo.defaultTreatment = CONTROL;
27
27
  }
@@ -7,7 +7,7 @@ import { IStorageAsync, IStorageSync } from '../storages/types';
7
7
  import { IEvaluationResult } from './types';
8
8
  import { SplitIO } from '../types';
9
9
  import { ILogger } from '../logger/types';
10
- import { returnSetsUnion } from '../utils/lang/sets';
10
+ import { returnSetsUnion, setToArray } from '../utils/lang/sets';
11
11
  import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
12
12
 
13
13
  const treatmentException = {
@@ -113,7 +113,7 @@ export function evaluateFeaturesByFlagSets(
113
113
  }
114
114
 
115
115
  return featureFlags.size ?
116
- evaluateFeatures(log, key, Array.from(featureFlags), attributes, storage) :
116
+ evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
117
117
  {};
118
118
  }
119
119
 
@@ -7,6 +7,8 @@ function splitsEventEmitterFactory(EventEmitter: new () => IEventEmitter): ISpli
7
7
  const splitsEventEmitter = objectAssign(new EventEmitter(), {
8
8
  splitsArrived: false,
9
9
  splitsCacheLoaded: false,
10
+ initialized: false,
11
+ initCallbacks: []
10
12
  });
11
13
 
12
14
  // `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
@@ -56,8 +58,8 @@ export function readinessManagerFactory(
56
58
  // emit SDK_READY_TIMED_OUT
57
59
  let hasTimedout = false;
58
60
 
59
- function timeout() {
60
- if (hasTimedout) return;
61
+ function timeout() { // eslint-disable-next-line no-use-before-define
62
+ if (hasTimedout || isReady) return;
61
63
  hasTimedout = true;
62
64
  syncLastUpdate();
63
65
  gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
@@ -65,7 +67,8 @@ export function readinessManagerFactory(
65
67
 
66
68
  let readyTimeoutId: ReturnType<typeof setTimeout>;
67
69
  if (readyTimeout > 0) {
68
- readyTimeoutId = setTimeout(timeout, readyTimeout);
70
+ if (splits.initialized) readyTimeoutId = setTimeout(timeout, readyTimeout);
71
+ else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
69
72
  }
70
73
 
71
74
  // emit SDK_READY and SDK_UPDATE
@@ -132,6 +135,12 @@ export function readinessManagerFactory(
132
135
  // tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
133
136
  setDestroyed() { isDestroyed = true; },
134
137
 
138
+ init() {
139
+ if (splits.initialized) return;
140
+ splits.initialized = true;
141
+ splits.initCallbacks.forEach(cb => cb());
142
+ },
143
+
135
144
  destroy() {
136
145
  isDestroyed = true;
137
146
  syncLastUpdate();
@@ -12,6 +12,8 @@ export interface ISplitsEventEmitter extends IEventEmitter {
12
12
  once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
13
13
  splitsArrived: boolean
14
14
  splitsCacheLoaded: boolean
15
+ initialized: boolean,
16
+ initCallbacks: (() => void)[]
15
17
  }
16
18
 
17
19
  /** Segments data emitter */
@@ -59,6 +61,7 @@ export interface IReadinessManager {
59
61
  timeout(): void,
60
62
  setDestroyed(): void,
61
63
  destroy(): void,
64
+ init(): void,
62
65
 
63
66
  /** for client-side */
64
67
  shared(): IReadinessManager,
@@ -75,8 +75,6 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
75
75
  validKey
76
76
  );
77
77
 
78
- sharedSyncManager && sharedSyncManager.start();
79
-
80
78
  log.info(NEW_SHARED_CLIENT);
81
79
  } else {
82
80
  log.debug(RETRIEVE_CLIENT_EXISTING);
@@ -7,7 +7,7 @@ import { IBasicClient, SplitIO } from '../types';
7
7
  import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
8
8
  import { createLoggerAPI } from '../logger/sdkLogger';
9
9
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
10
- import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
10
+ import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
11
11
  import { objectAssign } from '../utils/lang/objectAssign';
12
12
  import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
13
13
  import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
@@ -23,21 +23,27 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
23
23
  const { settings, platform, storageFactory, splitApiFactory, extraProps,
24
24
  syncManagerFactory, SignalListener, impressionsObserverFactory,
25
25
  integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
26
- filterAdapterFactory } = params;
26
+ filterAdapterFactory, lazyInit } = params;
27
27
  const { log, sync: { impressionsMode } } = settings;
28
28
 
29
29
  // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
30
30
  // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
31
31
 
32
- // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
33
- validateAndTrackApiKey(log, settings.core.authorizationKey);
32
+ // initialization
33
+ let hasInit = false;
34
+ const initCallbacks: (() => void)[] = [];
35
+
36
+ function whenInit(cb: () => void) {
37
+ if (hasInit) cb();
38
+ else initCallbacks.push(cb);
39
+ }
34
40
 
35
41
  const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
36
42
  const readiness = sdkReadinessManager.readinessManager;
37
43
 
38
44
  const storage = storageFactory({
39
45
  settings,
40
- onReadyCb: (error) => {
46
+ onReadyCb(error) {
41
47
  if (error) {
42
48
  // If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
43
49
  readiness.timeout();
@@ -46,8 +52,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
46
52
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
47
53
  readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
48
54
  },
55
+ onReadyFromCacheCb() {
56
+ readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
57
+ }
49
58
  });
50
- // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
59
+
51
60
  const clients: Record<string, IBasicClient> = {};
52
61
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
53
62
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
@@ -67,8 +76,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
67
76
  strategy = strategyDebugFactory(observer);
68
77
  }
69
78
 
70
- const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
71
- const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
79
+ const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
80
+ const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
72
81
 
73
82
  // splitApi is used by SyncManager and Browser signal listener
74
83
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
@@ -85,8 +94,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
85
94
  const clientMethod = sdkClientMethodFactory(ctx);
86
95
  const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
87
96
 
88
- syncManager && syncManager.start();
89
- signalListener && signalListener.start();
97
+
98
+ function init() {
99
+ if (hasInit) return;
100
+ hasInit = true;
101
+
102
+ // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
103
+ validateAndTrackApiKey(log, settings.core.authorizationKey);
104
+ readiness.init();
105
+ uniqueKeysTracker && uniqueKeysTracker.start();
106
+ syncManager && syncManager.start();
107
+ signalListener && signalListener.start();
108
+
109
+ initCallbacks.forEach((cb) => cb());
110
+ initCallbacks.length = 0;
111
+ }
90
112
 
91
113
  log.info(NEW_FACTORY);
92
114
 
@@ -107,7 +129,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
107
129
  settings,
108
130
 
109
131
  destroy() {
110
- return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
132
+ return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
111
133
  }
112
- }, extraProps && extraProps(ctx));
134
+ }, extraProps && extraProps(ctx), lazyInit ? { init } : init());
113
135
  }
@@ -68,6 +68,8 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
68
68
  * Object parameter with the modules required to create an SDK factory instance
69
69
  */
70
70
  export interface ISdkFactoryParams {
71
+ // If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
72
+ lazyInit?: boolean,
71
73
 
72
74
  // The settings must be already validated
73
75
  settings: ISettings,