@splitsoftware/splitio-commons 0.1.1-rc.20 → 1.0.1-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 (97) hide show
  1. package/CHANGES.txt +9 -0
  2. package/README.md +3 -2
  3. package/cjs/listeners/browser.js +3 -1
  4. package/cjs/logger/constants.js +4 -4
  5. package/cjs/logger/messages/error.js +2 -1
  6. package/cjs/logger/messages/warn.js +0 -1
  7. package/cjs/sdkClient/sdkClient.js +4 -4
  8. package/cjs/sdkClient/sdkClientMethodCS.js +16 -5
  9. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  10. package/cjs/sdkFactory/index.js +6 -3
  11. package/cjs/storages/inMemory/InMemoryStorage.js +0 -3
  12. package/cjs/storages/inRedis/index.js +1 -2
  13. package/cjs/storages/pluggable/SplitsCachePluggable.js +1 -1
  14. package/cjs/storages/pluggable/inMemoryWrapper.js +19 -5
  15. package/cjs/storages/pluggable/index.js +38 -15
  16. package/cjs/storages/pluggable/wrapperAdapter.js +3 -3
  17. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  18. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  19. package/cjs/sync/submitters/submitterManager.js +19 -0
  20. package/cjs/sync/syncManagerOnline.js +20 -26
  21. package/cjs/trackers/impressionObserver/utils.js +1 -1
  22. package/cjs/utils/constants/index.js +3 -2
  23. package/cjs/utils/settingsValidation/mode.js +1 -1
  24. package/cjs/utils/settingsValidation/storage/storageCS.js +17 -3
  25. package/esm/listeners/browser.js +3 -1
  26. package/esm/logger/constants.js +3 -3
  27. package/esm/logger/messages/error.js +2 -1
  28. package/esm/logger/messages/warn.js +0 -1
  29. package/esm/sdkClient/sdkClient.js +5 -5
  30. package/esm/sdkClient/sdkClientMethodCS.js +16 -5
  31. package/esm/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  32. package/esm/sdkFactory/index.js +6 -3
  33. package/esm/storages/inMemory/InMemoryStorage.js +0 -3
  34. package/esm/storages/inRedis/index.js +1 -2
  35. package/esm/storages/pluggable/SplitsCachePluggable.js +1 -1
  36. package/esm/storages/pluggable/inMemoryWrapper.js +19 -5
  37. package/esm/storages/pluggable/index.js +40 -16
  38. package/esm/storages/pluggable/wrapperAdapter.js +3 -3
  39. package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  40. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  41. package/esm/sync/submitters/submitterManager.js +15 -0
  42. package/esm/sync/syncManagerOnline.js +20 -26
  43. package/esm/trackers/impressionObserver/utils.js +2 -2
  44. package/esm/utils/constants/index.js +2 -1
  45. package/esm/utils/settingsValidation/mode.js +2 -2
  46. package/esm/utils/settingsValidation/storage/storageCS.js +19 -5
  47. package/package.json +1 -1
  48. package/src/listeners/browser.ts +3 -1
  49. package/src/logger/constants.ts +3 -3
  50. package/src/logger/messages/error.ts +2 -1
  51. package/src/logger/messages/warn.ts +0 -1
  52. package/src/sdkClient/sdkClient.ts +6 -6
  53. package/src/sdkClient/sdkClientMethodCS.ts +14 -4
  54. package/src/sdkClient/sdkClientMethodCSWithTT.ts +15 -5
  55. package/src/sdkFactory/index.ts +7 -3
  56. package/src/services/splitApi.ts +4 -1
  57. package/src/storages/inLocalStorage/index.ts +2 -2
  58. package/src/storages/inMemory/InMemoryStorage.ts +0 -3
  59. package/src/storages/inMemory/InMemoryStorageCS.ts +2 -2
  60. package/src/storages/inRedis/index.ts +1 -1
  61. package/src/storages/pluggable/EventsCachePluggable.ts +3 -3
  62. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -3
  63. package/src/storages/pluggable/SegmentsCachePluggable.ts +3 -3
  64. package/src/storages/pluggable/SplitsCachePluggable.ts +4 -4
  65. package/src/storages/pluggable/inMemoryWrapper.ts +20 -6
  66. package/src/storages/pluggable/index.ts +46 -16
  67. package/src/storages/pluggable/wrapperAdapter.ts +5 -5
  68. package/src/storages/types.ts +24 -24
  69. package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
  70. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +0 -1
  71. package/src/sync/submitters/submitterManager.ts +22 -0
  72. package/src/sync/syncManagerOnline.ts +26 -32
  73. package/src/sync/types.ts +1 -1
  74. package/src/trackers/impressionObserver/ImpressionObserver.ts +1 -1
  75. package/src/trackers/impressionObserver/utils.ts +2 -2
  76. package/src/types.ts +5 -5
  77. package/src/utils/constants/index.ts +6 -4
  78. package/src/utils/settingsValidation/mode.ts +2 -2
  79. package/src/utils/settingsValidation/storage/storageCS.ts +21 -8
  80. package/types/logger/constants.d.ts +3 -3
  81. package/types/services/splitApi.d.ts +1 -1
  82. package/types/storages/inMemory/InMemoryStorageCS.d.ts +2 -2
  83. package/types/storages/pluggable/EventsCachePluggable.d.ts +2 -2
  84. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +2 -2
  85. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +5 -5
  86. package/types/storages/pluggable/SplitsCachePluggable.d.ts +3 -3
  87. package/types/storages/pluggable/inMemoryWrapper.d.ts +6 -3
  88. package/types/storages/pluggable/index.d.ts +2 -2
  89. package/types/storages/pluggable/wrapperAdapter.d.ts +4 -4
  90. package/types/storages/types.d.ts +21 -22
  91. package/types/sync/submitters/submitterManager.d.ts +4 -0
  92. package/types/sync/syncManagerOnline.d.ts +1 -1
  93. package/types/sync/types.d.ts +1 -1
  94. package/types/trackers/impressionObserver/ImpressionObserver.d.ts +1 -1
  95. package/types/types.d.ts +5 -5
  96. package/types/utils/constants/index.d.ts +6 -4
  97. package/types/utils/settingsValidation/storage/storageCS.d.ts +6 -4
@@ -16,19 +16,33 @@ __InLocalStorageMockFactory.type = constants_2.STORAGE_MEMORY;
16
16
  *
17
17
  * @param {any} settings config object provided by the user to initialize the sdk
18
18
  *
19
- * @returns {Object} valid storage factory. It might be the default `InMemoryStorageCSFactory` if the provided storage is invalid.
19
+ * @returns {Object} valid storage factory. Default to `InMemoryStorageCSFactory` if the provided storage is invalid or not compatible with the sdk mode if mode is standalone or localhost
20
+ *
21
+ * @throws error if mode is consumer and the provided storage is not compatible
20
22
  */
21
23
  function validateStorageCS(settings) {
22
24
  var _a = settings.storage, storage = _a === void 0 ? InMemoryStorageCS_1.InMemoryStorageCSFactory : _a, log = settings.log, mode = settings.mode;
23
25
  // If an invalid storage is provided, fallback into MEMORY
24
- if (typeof storage !== 'function' || storage.type !== constants_2.STORAGE_MEMORY && storage.type !== constants_2.STORAGE_LOCALSTORAGE) {
26
+ if (typeof storage !== 'function' || [constants_2.STORAGE_MEMORY, constants_2.STORAGE_LOCALSTORAGE, constants_2.STORAGE_PLUGGABLE].indexOf(storage.type) === -1) {
25
27
  storage = InMemoryStorageCS_1.InMemoryStorageCSFactory;
26
- log.warn(constants_1.WARN_STORAGE_INVALID);
28
+ log.error(constants_1.ERROR_STORAGE_INVALID);
27
29
  }
28
30
  // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
29
31
  if (mode === constants_2.LOCALHOST_MODE && storage.type === constants_2.STORAGE_LOCALSTORAGE) {
30
32
  return __InLocalStorageMockFactory;
31
33
  }
34
+ if ([constants_2.LOCALHOST_MODE, constants_2.STANDALONE_MODE].indexOf(mode) === -1) {
35
+ // Consumer modes require an async storage
36
+ if (storage.type !== constants_2.STORAGE_PLUGGABLE)
37
+ throw new Error('A PluggableStorage instance is required on consumer mode');
38
+ }
39
+ else {
40
+ // Standalone and localhost modes require a sync storage
41
+ if (storage.type === constants_2.STORAGE_PLUGGABLE) {
42
+ storage = InMemoryStorageCS_1.InMemoryStorageCSFactory;
43
+ log.error(constants_1.ERROR_STORAGE_INVALID, [' It requires consumer mode.']);
44
+ }
45
+ }
32
46
  // return default InMemory storage if provided one is not valid
33
47
  return storage;
34
48
  }
@@ -46,6 +46,8 @@ var BrowserSignalListener = /** @class */ (function () {
46
46
  * using beacon API if possible, or falling back to regular post transport.
47
47
  */
48
48
  BrowserSignalListener.prototype.flushData = function () {
49
+ if (!this.syncManager)
50
+ return; // In consumer mode there is not sync manager and data to flush
49
51
  var eventsUrl = this.settings.urls.events;
50
52
  var extraMetadata = {
51
53
  // sim stands for Sync/Split Impressions Mode
@@ -56,7 +58,7 @@ var BrowserSignalListener = /** @class */ (function () {
56
58
  if (this.storage.impressionCounts)
57
59
  this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
58
60
  // Close streaming connection
59
- if (this.syncManager && this.syncManager.pushManager)
61
+ if (this.syncManager.pushManager)
60
62
  this.syncManager.pushManager.stop();
61
63
  };
62
64
  BrowserSignalListener.prototype._flushData = function (url, cache, postService, fromCacheToPayload, extraMetadata) {
@@ -89,9 +89,8 @@ export var WARN_INTEGRATION_INVALID = 218;
89
89
  export var WARN_SPLITS_FILTER_IGNORED = 219;
90
90
  export var WARN_SPLITS_FILTER_INVALID = 220;
91
91
  export var WARN_SPLITS_FILTER_EMPTY = 221;
92
- export var WARN_STORAGE_INVALID = 222;
93
- export var WARN_API_KEY = 223;
94
- export var STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 224;
92
+ export var WARN_API_KEY = 222;
93
+ export var STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
95
94
  export var ERROR_ENGINE_COMBINER_IFELSEIF = 300;
96
95
  export var ERROR_LOGLEVEL_INVALID = 301;
97
96
  export var ERROR_CLIENT_LISTENER = 302;
@@ -116,6 +115,7 @@ export var ERROR_EMPTY_ARRAY = 320;
116
115
  export var ERROR_INVALID_IMPRESSIONS_MODE = 321;
117
116
  export var ERROR_HTTP = 322;
118
117
  export var ERROR_LOCALHOST_MODULE_REQUIRED = 323;
118
+ export var ERROR_STORAGE_INVALID = 324;
119
119
  // Log prefixes (a.k.a. tags or categories)
120
120
  export var LOG_PREFIX_SETTINGS = 'settings';
121
121
  export var LOG_PREFIX_INSTANTIATION = 'Factory instantiation';
@@ -29,5 +29,6 @@ export var codesError = [
29
29
  [c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'],
30
30
  // initialization / settings validation
31
31
  [c.ERROR_INVALID_IMPRESSIONS_MODE, c.LOG_PREFIX_SETTINGS + ': you passed an invalid "impressionsMode". It should be one of the following values: %s. Defaulting to "%s" mode.'],
32
- [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.']
32
+ [c.ERROR_LOCALHOST_MODULE_REQUIRED, c.LOG_PREFIX_SETTINGS + ': an invalid value was received for "sync.localhostMode" config. A valid entity should be provided for localhost mode.'],
33
+ [c.ERROR_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS + ': The provided storage is invalid.%s Fallbacking into default MEMORY storage'],
33
34
  ];
@@ -28,7 +28,6 @@ export var codesWarn = codesError.concat([
28
28
  [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS + ': split filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'],
29
29
  [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS + ': split filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'],
30
30
  [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS + ': splitFilters configuration must be a non-empty array of filter objects.'],
31
- [c.WARN_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS + ': The provided storage is invalid. Fallbacking into default MEMORY storage'],
32
31
  [c.WARN_API_KEY, c.LOG_PREFIX_SETTINGS + ': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
33
32
  [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
34
33
  ]);
@@ -1,5 +1,5 @@
1
1
  import objectAssign from 'object-assign';
2
- import { CONSUMER_MODE } from '../utils/constants';
2
+ import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants';
3
3
  import { releaseApiKey } from '../utils/inputValidation/apiKey';
4
4
  import clientFactory from './client';
5
5
  import clientInputValidationDecorator from './clientInputValidation';
@@ -13,8 +13,8 @@ export function sdkClientFactory(params) {
13
13
  Object.create(sdkReadinessManager.sdkStatus),
14
14
  // Client API (getTreatment* & track methods)
15
15
  clientInputValidationDecorator(settings.log, clientFactory(params), sdkReadinessManager.readinessManager,
16
- // @TODO isStorageSync could be extracted from the storage itself (e.g. `storage.isSync`) to simplify interfaces
17
- settings.mode === CONSUMER_MODE ? false : true),
16
+ // storage is async if and only if mode is consumer or partial consumer
17
+ [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false),
18
18
  // Sdk destroy
19
19
  {
20
20
  destroy: function () {
@@ -25,11 +25,11 @@ export function sdkClientFactory(params) {
25
25
  // Cleanup event listeners
26
26
  sdkReadinessManager.readinessManager.destroy();
27
27
  signalListener && signalListener.stop();
28
- // Cleanup storage
29
- storage.destroy();
30
28
  // Release the API Key if it is the main client
31
29
  if (!sharedClient)
32
30
  releaseApiKey(settings.core.authorizationKey);
31
+ // Cleanup storage
32
+ return storage.destroy();
33
33
  });
34
34
  }
35
35
  });
@@ -4,6 +4,7 @@ import { getMatching, keyParser } from '../utils/key';
4
4
  import { sdkClientFactory } from './sdkClient';
5
5
  import objectAssign from 'object-assign';
6
6
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
7
+ import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
7
8
  function buildInstanceId(key) {
8
9
  // @ts-ignore
9
10
  return (key.matchingKey ? key.matchingKey : key) + "-" + (key.bucketingKey ? key.bucketingKey : key) + "-";
@@ -39,14 +40,24 @@ export function sdkClientMethodCSFactory(params) {
39
40
  var instanceId = buildInstanceId(validKey);
40
41
  if (!clientInstances[instanceId]) {
41
42
  var matchingKey = getMatching(validKey);
42
- var sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
43
- var sharedStorage = storage.shared(matchingKey);
44
- var sharedSyncManager = syncManager && syncManager.shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
43
+ var sharedSdkReadiness_1 = sdkReadinessManager.shared(readyTimeout);
44
+ var sharedStorage = storage.shared && storage.shared(matchingKey, function (err) {
45
+ if (err)
46
+ return;
47
+ // Emit SDK_READY in consumer mode for shared clients
48
+ sharedSdkReadiness_1.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
49
+ });
50
+ // 3 possibilities:
51
+ // - Standalone mode: both syncManager and sharedSyncManager are defined
52
+ // - Consumer mode: both syncManager and sharedSyncManager are undefined
53
+ // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
54
+ // @ts-ignore
55
+ var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
45
56
  // As shared clients reuse all the storage information, we don't need to check here if we
46
57
  // will use offline or online mode. We should stick with the original decision.
47
58
  clientInstances[instanceId] = clientCSDecorator(sdkClientFactory(objectAssign({}, params, {
48
- sdkReadinessManager: sharedSdkReadiness,
49
- storage: sharedStorage,
59
+ sdkReadinessManager: sharedSdkReadiness_1,
60
+ storage: sharedStorage || storage,
50
61
  syncManager: sharedSyncManager,
51
62
  signalListener: undefined,
52
63
  sharedClient: true
@@ -5,6 +5,7 @@ import { getMatching, keyParser } from '../utils/key';
5
5
  import { sdkClientFactory } from './sdkClient';
6
6
  import objectAssign from 'object-assign';
7
7
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
8
+ import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
8
9
  function buildInstanceId(key, trafficType) {
9
10
  // @ts-ignore
10
11
  return (key.matchingKey ? key.matchingKey : key) + "-" + (key.bucketingKey ? key.bucketingKey : key) + "-" + (trafficType !== undefined ? trafficType : '');
@@ -52,19 +53,29 @@ export function sdkClientMethodCSFactory(params) {
52
53
  var instanceId = buildInstanceId(validKey, validTrafficType);
53
54
  if (!clientInstances[instanceId]) {
54
55
  var matchingKey = getMatching(validKey);
55
- var sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
56
- var sharedStorage = storage.shared(matchingKey);
57
- var sharedSyncManager = syncManager.shared(matchingKey, sharedSdkReadiness.readinessManager, sharedStorage);
56
+ var sharedSdkReadiness_1 = sdkReadinessManager.shared(readyTimeout);
57
+ var sharedStorage = storage.shared && storage.shared(matchingKey, function (err) {
58
+ if (err)
59
+ return;
60
+ // Emit SDK_READY in consumer mode for shared clients
61
+ sharedSdkReadiness_1.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
62
+ });
63
+ // 3 possibilities:
64
+ // - Standalone mode: both syncManager and sharedSyncManager are defined
65
+ // - Consumer mode: both syncManager and sharedSyncManager are undefined
66
+ // - Consumer partial mode: syncManager is defined (only for submitters) but sharedSyncManager is undefined
67
+ // @ts-ignore
68
+ var sharedSyncManager = syncManager && sharedStorage && syncManager.shared(matchingKey, sharedSdkReadiness_1.readinessManager, sharedStorage);
58
69
  // As shared clients reuse all the storage information, we don't need to check here if we
59
70
  // will use offline or online mode. We should stick with the original decision.
60
71
  clientInstances[instanceId] = clientCSDecorator(sdkClientFactory(objectAssign({}, params, {
61
- sdkReadinessManager: sharedSdkReadiness,
62
- storage: sharedStorage,
72
+ sdkReadinessManager: sharedSdkReadiness_1,
73
+ storage: sharedStorage || storage,
63
74
  syncManager: sharedSyncManager,
64
75
  signalListener: undefined,
65
76
  sharedClient: true
66
77
  })), validKey, validTrafficType);
67
- sharedSyncManager.start();
78
+ sharedSyncManager && sharedSyncManager.start();
68
79
  log.info(NEW_SHARED_CLIENT);
69
80
  }
70
81
  else {
@@ -27,13 +27,16 @@ export function sdkFactory(params) {
27
27
  // ATM, only used by InLocalStorage
28
28
  matchingKey: getMatching(settings.core.key),
29
29
  splitFiltersValidation: settings.sync.__splitFiltersValidation,
30
- // Callback used in consumer mode (`syncManagerFactory` is undefined) to emit SDK_READY
31
- onReadyCb: !syncManagerFactory ? function (error) {
30
+ // ATM, only used by PluggableStorage
31
+ mode: settings.mode,
32
+ // Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined
33
+ // or only instantiates submitters, and therefore it is not able to emit readiness events.
34
+ onReadyCb: function (error) {
32
35
  if (error)
33
36
  return; // don't emit SDK_READY if storage failed to connect.
34
37
  readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
35
38
  readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
36
- } : undefined,
39
+ },
37
40
  metadata: metadataBuilder(settings),
38
41
  log: log
39
42
  };
@@ -10,9 +10,6 @@ import { STORAGE_MEMORY } from '../../utils/constants';
10
10
  * @param params parameters required by EventsCacheSync
11
11
  */
12
12
  export function InMemoryStorageFactory(params) {
13
- // InMemory storage is always ready
14
- if (params.onReadyCb)
15
- setTimeout(params.onReadyCb);
16
13
  return {
17
14
  splits: new SplitsCacheInMemory(),
18
15
  segments: new SegmentsCacheInMemory(),
@@ -21,8 +21,7 @@ export function InRedisStorage(options) {
21
21
  var redisClient = new RedisAdapter(log, options.options || {});
22
22
  // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
23
23
  redisClient.on('connect', function () {
24
- if (onReadyCb)
25
- onReadyCb();
24
+ onReadyCb();
26
25
  });
27
26
  return {
28
27
  splits: new SplitsCacheInRedis(log, keys, redisClient),
@@ -8,7 +8,7 @@ import AbstractSplitsCacheAsync from '../AbstractSplitsCacheAsync';
8
8
  var SplitsCachePluggable = /** @class */ (function (_super) {
9
9
  __extends(SplitsCachePluggable, _super);
10
10
  /**
11
- * Create a SplitsCache that uses a custom storage wrapper.
11
+ * Create a SplitsCache that uses a storage wrapper.
12
12
  * @param log Logger instance.
13
13
  * @param keys Key builder.
14
14
  * @param wrapper Adapted wrapper storage.
@@ -1,12 +1,15 @@
1
1
  import { startsWith, toNumber } from '../../utils/lang';
2
2
  import { setToArray, _Set } from '../../utils/lang/sets';
3
3
  /**
4
- * Creates a ICustomStorageWrapper implementation that stores items in memory.
4
+ * Creates a IPluggableStorageWrapper implementation that stores items in memory.
5
5
  * The `_cache` property is the object were items are stored.
6
6
  * Intended for testing purposes.
7
+ *
8
+ * @param connDelay delay in millis for `connect` resolve. If not provided, `connect` resolves inmediatelly.
7
9
  */
8
- export function inMemoryWrapperFactory() {
10
+ export function inMemoryWrapperFactory(connDelay) {
9
11
  var _cache = {};
12
+ var _connDelay = connDelay;
10
13
  return {
11
14
  /** Holds items (for key-value operations), list of items (for list operations) and set of items (for set operations) */
12
15
  _cache: _cache,
@@ -114,8 +117,19 @@ export function inMemoryWrapperFactory() {
114
117
  return Promise.resolve(setToArray(set));
115
118
  return Promise.reject('key is not a set');
116
119
  },
117
- // always connects and close
118
- connect: function () { return Promise.resolve(); },
119
- close: function () { return Promise.resolve(); },
120
+ // always connects and disconnects
121
+ connect: function () {
122
+ if (typeof _connDelay === 'number') {
123
+ return new Promise(function (res) { return setTimeout(res, _connDelay); });
124
+ }
125
+ else {
126
+ return Promise.resolve();
127
+ }
128
+ },
129
+ disconnect: function () { return Promise.resolve(); },
130
+ // for testing
131
+ _setConnDelay: function (connDelay) {
132
+ _connDelay = connDelay;
133
+ }
120
134
  };
121
135
  }
@@ -1,3 +1,4 @@
1
+ import { __assign } from "tslib";
1
2
  import KeyBuilderSS from '../KeyBuilderSS';
2
3
  import { SplitsCachePluggable } from './SplitsCachePluggable';
3
4
  import { SegmentsCachePluggable } from './SegmentsCachePluggable';
@@ -6,8 +7,11 @@ import { EventsCachePluggable } from './EventsCachePluggable';
6
7
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
7
8
  import { isObject } from '../../utils/lang';
8
9
  import { validatePrefix } from '../KeyBuilder';
9
- import { STORAGE_CUSTOM } from '../../utils/constants';
10
- var NO_VALID_WRAPPER = 'Expecting custom storage `wrapper` in options, but no valid wrapper instance was provided.';
10
+ import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
11
+ import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory';
12
+ import EventsCacheInMemory from '../inMemory/EventsCacheInMemory';
13
+ import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory';
14
+ var NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
11
15
  var NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
12
16
  /**
13
17
  * Validate pluggable storage factory options.
@@ -23,6 +27,23 @@ function validatePluggableStorageOptions(options) {
23
27
  if (missingMethods.length)
24
28
  throw new Error(NO_VALID_WRAPPER_INTERFACE + " The following methods are missing or invalid: " + missingMethods);
25
29
  }
30
+ // subscription to wrapper connect event in order to emit SDK_READY event
31
+ function wrapperConnect(wrapper, onReadyCb) {
32
+ wrapper.connect().then(function () {
33
+ onReadyCb();
34
+ }).catch(function (e) {
35
+ onReadyCb(e || new Error('Error connecting wrapper'));
36
+ });
37
+ }
38
+ // Async return type in `client.track` method on consumer partial mode
39
+ // No need to promisify impressions cache
40
+ function promisifyEventsTrack(events) {
41
+ var origTrack = events.track;
42
+ events.track = function () {
43
+ return Promise.resolve(origTrack.apply(this, arguments));
44
+ };
45
+ return events;
46
+ }
26
47
  /**
27
48
  * Pluggable storage factory for consumer server-side & client-side SplitFactory.
28
49
  */
@@ -30,29 +51,32 @@ export function PluggableStorage(options) {
30
51
  validatePluggableStorageOptions(options);
31
52
  var prefix = validatePrefix(options.prefix);
32
53
  function PluggableStorageFactory(_a) {
33
- var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb;
54
+ var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb, mode = _a.mode, eventsQueueSize = _a.eventsQueueSize, optimize = _a.optimize;
34
55
  var keys = new KeyBuilderSS(prefix, metadata);
35
56
  var wrapper = wrapperAdapter(log, options.wrapper);
36
- // subscription to Wrapper connect event in order to emit SDK_READY event
37
- wrapper.connect().then(function () {
38
- if (onReadyCb)
39
- onReadyCb();
40
- }).catch(function (e) {
41
- if (onReadyCb)
42
- onReadyCb(e || new Error('Error connecting wrapper'));
43
- });
57
+ var isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
58
+ // Connects to wrapper and emits SDK_READY event on main client
59
+ wrapperConnect(wrapper, onReadyCb);
44
60
  return {
45
61
  splits: new SplitsCachePluggable(log, keys, wrapper),
46
62
  segments: new SegmentsCachePluggable(log, keys, wrapper),
47
- impressions: new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
48
- events: new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
63
+ impressions: isPartialConsumer ? new ImpressionsCacheInMemory() : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
64
+ impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
65
+ events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
49
66
  // @TODO add telemetry cache when required
50
- // Disconnect the underlying storage, to release its resources (such as open files, database connections, etc).
67
+ // Disconnect the underlying storage
51
68
  destroy: function () {
52
- return wrapper.close();
69
+ return wrapper.disconnect();
70
+ },
71
+ // emits SDK_READY event on shared clients and returns a reference to the storage
72
+ shared: function (_, onReadyCb) {
73
+ wrapperConnect(wrapper, onReadyCb);
74
+ return __assign(__assign({}, this), {
75
+ // no-op destroy, to disconnect the wrapper only when the main client is destroyed
76
+ destroy: function () { } });
53
77
  }
54
78
  };
55
79
  }
56
- PluggableStorageFactory.type = STORAGE_CUSTOM;
80
+ PluggableStorageFactory.type = STORAGE_PLUGGABLE;
57
81
  return PluggableStorageFactory;
58
82
  }
@@ -16,14 +16,14 @@ export var METHODS_TO_PROMISE_WRAP = [
16
16
  'removeItems',
17
17
  'getItems',
18
18
  'connect',
19
- 'close'
19
+ 'disconnect'
20
20
  ];
21
21
  /**
22
- * Adapter of the Custom Storage Wrapper.
22
+ * Adapter of the Pluggable Storage Wrapper.
23
23
  * Used to handle exceptions as rejected promises, in order to simplify the error handling on storages.
24
24
  *
25
25
  * @param log logger instance
26
- * @param wrapper custom storage wrapper to adapt
26
+ * @param wrapper storage wrapper to adapt
27
27
  * @returns an adapted version of the given storage wrapper
28
28
  */
29
29
  export function wrapperAdapter(log, wrapper) {
@@ -19,7 +19,7 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, splitsCache, my
19
19
  // NOTE: We only collect metrics on startup.
20
20
  // mySegmentsPromise = tracker.start(tracker.TaskNames.MY_SEGMENTS_FETCH, startingUp ? metricCollectors : false, mySegmentsPromise);
21
21
  }
22
- // @TODO if allowing custom storages, handle async execution
22
+ // @TODO if allowing pluggable storages, handle async execution
23
23
  function updateSegments(segmentsData) {
24
24
  var shouldNotifyUpdate;
25
25
  if (Array.isArray(segmentsData)) {
@@ -67,7 +67,6 @@ var SplitsUpdateWorker = /** @class */ (function () {
67
67
  */
68
68
  SplitsUpdateWorker.prototype.killSplit = function (_a) {
69
69
  var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
70
- // @TODO handle retry due to errors in storage, once we allow the definition of custom async storages
71
70
  if (this.splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
72
71
  // trigger an SDK_UPDATE if Split was killed locally
73
72
  this.splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true);
@@ -0,0 +1,15 @@
1
+ import { syncTaskComposite } from '../syncTaskComposite';
2
+ import { eventsSyncTaskFactory } from './eventsSyncTask';
3
+ import { impressionsSyncTaskFactory } from './impressionsSyncTask';
4
+ import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask';
5
+ export function submitterManagerFactory(settings, storage, splitApi) {
6
+ var log = settings.log;
7
+ var submitters = [
8
+ impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
9
+ eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow)
10
+ // @TODO add telemetry submitter
11
+ ];
12
+ if (storage.impressionCounts)
13
+ submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts));
14
+ return syncTaskComposite(submitters);
15
+ }
@@ -1,7 +1,4 @@
1
- import { syncTaskComposite } from './syncTaskComposite';
2
- import { eventsSyncTaskFactory } from './submitters/eventsSyncTask';
3
- import { impressionsSyncTaskFactory } from './submitters/impressionsSyncTask';
4
- import { impressionCountsSyncTaskFactory } from './submitters/impressionCountsSyncTask';
1
+ import { submitterManagerFactory } from './submitters/submitterManager';
5
2
  import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
6
3
  import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
7
4
  /**
@@ -20,21 +17,14 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
20
17
  var settings = _a.settings, platform = _a.platform, splitApi = _a.splitApi, storage = _a.storage, readiness = _a.readiness;
21
18
  var log = settings.log;
22
19
  /** Polling Manager */
23
- var pollingManager = pollingManagerFactory(splitApi, storage, readiness, settings);
20
+ var pollingManager = pollingManagerFactory && pollingManagerFactory(splitApi, storage, readiness, settings);
24
21
  /** Push Manager */
25
- var pushManager = settings.streamingEnabled && pushManagerFactory ?
22
+ var pushManager = settings.streamingEnabled && pollingManager && pushManagerFactory ?
26
23
  pushManagerFactory(pollingManager, storage, readiness, splitApi.fetchAuth, platform, settings) :
27
24
  undefined;
28
25
  /** Submitter Manager */
29
- // It is not inyected via a factory as push and polling managers, because at the moment it is mandatory and the same for server-side and client-side variants
30
- var submitters = [
31
- impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
32
- eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow)
33
- // @TODO add telemetry submitter
34
- ];
35
- if (storage.impressionCounts)
36
- submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts));
37
- var submitter = syncTaskComposite(submitters);
26
+ // It is not inyected as push and polling managers, because at the moment it is required
27
+ var submitter = submitterManagerFactory(settings, storage, splitApi);
38
28
  /** Sync Manager logic */
39
29
  function startPolling() {
40
30
  if (!pollingManager.isRunning()) {
@@ -66,16 +56,18 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
66
56
  */
67
57
  start: function () {
68
58
  // start syncing splits and segments
69
- if (pushManager) {
70
- // Doesn't call `syncAll` when the syncManager is resuming
71
- if (startFirstTime) {
72
- pollingManager.syncAll();
73
- startFirstTime = false;
59
+ if (pollingManager) {
60
+ if (pushManager) {
61
+ // Doesn't call `syncAll` when the syncManager is resuming
62
+ if (startFirstTime) {
63
+ pollingManager.syncAll();
64
+ startFirstTime = false;
65
+ }
66
+ pushManager.start();
67
+ }
68
+ else {
69
+ pollingManager.start();
74
70
  }
75
- pushManager.start();
76
- }
77
- else {
78
- pollingManager.start();
79
71
  }
80
72
  // start periodic data recording (events, impressions, telemetry).
81
73
  submitter && submitter.start();
@@ -88,7 +80,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
88
80
  // stop syncing
89
81
  if (pushManager)
90
82
  pushManager.stop();
91
- if (pollingManager.isRunning())
83
+ if (pollingManager && pollingManager.isRunning())
92
84
  pollingManager.stop();
93
85
  // stop periodic data recording (events, impressions, telemetry).
94
86
  if (submitter)
@@ -105,8 +97,10 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
105
97
  return Promise.resolve();
106
98
  },
107
99
  // [Only used for client-side]
108
- // It assumes that polling and push managers implement the interfaces for client-side
100
+ // If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
109
101
  shared: function (matchingKey, readinessManager, storage) {
102
+ if (!pollingManager)
103
+ return;
110
104
  var mySegmentsSyncTask = pollingManager.add(matchingKey, readinessManager, storage);
111
105
  return {
112
106
  isRunning: mySegmentsSyncTask.isRunning,
@@ -1,9 +1,9 @@
1
- import { OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
1
+ import { CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
2
2
  /**
3
3
  * Checks if impressions previous time should be added or not.
4
4
  */
5
5
  export function shouldAddPt(settings) {
6
- return [PRODUCER_MODE, STANDALONE_MODE].indexOf(settings.mode) > -1 ? true : false;
6
+ return [PRODUCER_MODE, STANDALONE_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) > -1 ? true : false;
7
7
  }
8
8
  /**
9
9
  * Checks if it should dedupe impressions or not.
@@ -18,8 +18,9 @@ export var LOCALHOST_MODE = 'localhost';
18
18
  export var STANDALONE_MODE = 'standalone';
19
19
  export var PRODUCER_MODE = 'producer';
20
20
  export var CONSUMER_MODE = 'consumer';
21
+ export var CONSUMER_PARTIAL_MODE = 'consumer_partial';
21
22
  // Storage types
22
23
  export var STORAGE_MEMORY = 'MEMORY';
23
24
  export var STORAGE_LOCALSTORAGE = 'LOCALSTORAGE';
24
25
  export var STORAGE_REDIS = 'REDIS';
25
- export var STORAGE_CUSTOM = 'CUSTOM';
26
+ export var STORAGE_PLUGGABLE = 'PLUGGABLE';
@@ -1,9 +1,9 @@
1
- import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE } from '../constants';
1
+ import { LOCALHOST_MODE, STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../constants';
2
2
  export default function mode(key, mode) {
3
3
  // Leaving the comparison as is, in case we change the mode name but not the setting.
4
4
  if (key === 'localhost')
5
5
  return LOCALHOST_MODE;
6
- if ([STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE].indexOf(mode) === -1)
6
+ if ([STANDALONE_MODE, PRODUCER_MODE, CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(mode) === -1)
7
7
  throw Error('Invalid mode provided');
8
8
  return mode;
9
9
  }
@@ -1,6 +1,6 @@
1
1
  import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
2
- import { WARN_STORAGE_INVALID } from '../../../logger/constants';
3
- import { LOCALHOST_MODE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
2
+ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
3
+ import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
4
4
  export function __InLocalStorageMockFactory(params) {
5
5
  var result = InMemoryStorageCSFactory(params);
6
6
  result.splits.checkCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
@@ -12,19 +12,33 @@ __InLocalStorageMockFactory.type = STORAGE_MEMORY;
12
12
  *
13
13
  * @param {any} settings config object provided by the user to initialize the sdk
14
14
  *
15
- * @returns {Object} valid storage factory. It might be the default `InMemoryStorageCSFactory` if the provided storage is invalid.
15
+ * @returns {Object} valid storage factory. Default to `InMemoryStorageCSFactory` if the provided storage is invalid or not compatible with the sdk mode if mode is standalone or localhost
16
+ *
17
+ * @throws error if mode is consumer and the provided storage is not compatible
16
18
  */
17
19
  export function validateStorageCS(settings) {
18
20
  var _a = settings.storage, storage = _a === void 0 ? InMemoryStorageCSFactory : _a, log = settings.log, mode = settings.mode;
19
21
  // If an invalid storage is provided, fallback into MEMORY
20
- if (typeof storage !== 'function' || storage.type !== STORAGE_MEMORY && storage.type !== STORAGE_LOCALSTORAGE) {
22
+ if (typeof storage !== 'function' || [STORAGE_MEMORY, STORAGE_LOCALSTORAGE, STORAGE_PLUGGABLE].indexOf(storage.type) === -1) {
21
23
  storage = InMemoryStorageCSFactory;
22
- log.warn(WARN_STORAGE_INVALID);
24
+ log.error(ERROR_STORAGE_INVALID);
23
25
  }
24
26
  // In localhost mode with InLocalStorage, fallback to a mock InLocalStorage to emit SDK_READY_FROM_CACHE
25
27
  if (mode === LOCALHOST_MODE && storage.type === STORAGE_LOCALSTORAGE) {
26
28
  return __InLocalStorageMockFactory;
27
29
  }
30
+ if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
31
+ // Consumer modes require an async storage
32
+ if (storage.type !== STORAGE_PLUGGABLE)
33
+ throw new Error('A PluggableStorage instance is required on consumer mode');
34
+ }
35
+ else {
36
+ // Standalone and localhost modes require a sync storage
37
+ if (storage.type === STORAGE_PLUGGABLE) {
38
+ storage = InMemoryStorageCSFactory;
39
+ log.error(ERROR_STORAGE_INVALID, [' It requires consumer mode.']);
40
+ }
41
+ }
28
42
  // return default InMemory storage if provided one is not valid
29
43
  return storage;
30
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@splitsoftware/splitio-commons",
3
- "version": "0.1.1-rc.20",
3
+ "version": "1.0.1-rc.2",
4
4
  "description": "Split Javascript SDK common components",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",