@splitsoftware/splitio-commons 1.0.0 → 1.0.1-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.
Files changed (116) hide show
  1. package/README.md +1 -1
  2. package/cjs/listeners/browser.js +3 -1
  3. package/cjs/logger/constants.js +4 -4
  4. package/cjs/logger/messages/error.js +2 -1
  5. package/cjs/logger/messages/warn.js +0 -1
  6. package/cjs/sdkClient/sdkClient.js +4 -4
  7. package/cjs/sdkClient/sdkClientMethodCS.js +16 -5
  8. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  9. package/cjs/sdkFactory/index.js +6 -3
  10. package/cjs/storages/inMemory/InMemoryStorage.js +0 -3
  11. package/cjs/storages/inRedis/index.js +1 -2
  12. package/cjs/storages/pluggable/SplitsCachePluggable.js +1 -1
  13. package/cjs/storages/pluggable/constants.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/SSEClient/index.js +0 -1
  19. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  20. package/cjs/sync/submitters/submitterManager.js +19 -0
  21. package/cjs/sync/syncManagerOnline.js +20 -26
  22. package/cjs/trackers/impressionObserver/utils.js +1 -1
  23. package/cjs/utils/MinEventEmitter.js +5 -5
  24. package/cjs/utils/constants/index.js +3 -2
  25. package/cjs/utils/env/isNode.js +4 -1
  26. package/cjs/utils/settingsValidation/mode.js +1 -1
  27. package/cjs/utils/settingsValidation/storage/storageCS.js +17 -3
  28. package/esm/listeners/browser.js +3 -1
  29. package/esm/logger/constants.js +3 -3
  30. package/esm/logger/messages/error.js +2 -1
  31. package/esm/logger/messages/warn.js +0 -1
  32. package/esm/sdkClient/sdkClient.js +5 -5
  33. package/esm/sdkClient/sdkClientMethodCS.js +16 -5
  34. package/esm/sdkClient/sdkClientMethodCSWithTT.js +17 -6
  35. package/esm/sdkFactory/index.js +6 -3
  36. package/esm/storages/inMemory/InMemoryStorage.js +0 -3
  37. package/esm/storages/inRedis/index.js +1 -2
  38. package/esm/storages/pluggable/SplitsCachePluggable.js +1 -1
  39. package/esm/storages/pluggable/constants.js +1 -1
  40. package/esm/storages/pluggable/inMemoryWrapper.js +19 -5
  41. package/esm/storages/pluggable/index.js +40 -16
  42. package/esm/storages/pluggable/wrapperAdapter.js +3 -3
  43. package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  44. package/esm/sync/streaming/SSEClient/index.js +0 -1
  45. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
  46. package/esm/sync/submitters/submitterManager.js +15 -0
  47. package/esm/sync/syncManagerOnline.js +20 -26
  48. package/esm/trackers/impressionObserver/utils.js +2 -2
  49. package/esm/utils/MinEventEmitter.js +5 -5
  50. package/esm/utils/constants/index.js +2 -1
  51. package/esm/utils/env/isNode.js +4 -1
  52. package/esm/utils/settingsValidation/mode.js +2 -2
  53. package/esm/utils/settingsValidation/storage/storageCS.js +19 -5
  54. package/package.json +3 -4
  55. package/src/listeners/browser.ts +3 -1
  56. package/src/logger/constants.ts +3 -3
  57. package/src/logger/messages/error.ts +2 -1
  58. package/src/logger/messages/warn.ts +0 -1
  59. package/src/sdkClient/sdkClient.ts +6 -6
  60. package/src/sdkClient/sdkClientMethodCS.ts +14 -4
  61. package/src/sdkClient/sdkClientMethodCSWithTT.ts +15 -5
  62. package/src/sdkFactory/index.ts +7 -3
  63. package/src/sdkFactory/types.ts +2 -2
  64. package/src/services/splitApi.ts +4 -1
  65. package/src/services/types.ts +16 -2
  66. package/src/storages/inLocalStorage/index.ts +2 -2
  67. package/src/storages/inMemory/InMemoryStorage.ts +0 -3
  68. package/src/storages/inMemory/InMemoryStorageCS.ts +2 -2
  69. package/src/storages/inRedis/index.ts +1 -1
  70. package/src/storages/pluggable/EventsCachePluggable.ts +3 -3
  71. package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -3
  72. package/src/storages/pluggable/SegmentsCachePluggable.ts +3 -3
  73. package/src/storages/pluggable/SplitsCachePluggable.ts +4 -4
  74. package/src/storages/pluggable/constants.ts +1 -1
  75. package/src/storages/pluggable/inMemoryWrapper.ts +20 -6
  76. package/src/storages/pluggable/index.ts +46 -16
  77. package/src/storages/pluggable/wrapperAdapter.ts +5 -5
  78. package/src/storages/types.ts +24 -24
  79. package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
  80. package/src/sync/streaming/SSEClient/index.ts +5 -5
  81. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +0 -1
  82. package/src/sync/submitters/submitterManager.ts +22 -0
  83. package/src/sync/syncManagerOnline.ts +26 -32
  84. package/src/sync/types.ts +1 -1
  85. package/src/trackers/impressionObserver/ImpressionObserver.ts +1 -1
  86. package/src/trackers/impressionObserver/utils.ts +2 -2
  87. package/src/types.ts +14 -13
  88. package/src/utils/MinEventEmitter.ts +10 -10
  89. package/src/utils/constants/index.ts +6 -4
  90. package/src/utils/env/isNode.ts +4 -1
  91. package/src/utils/settingsValidation/mode.ts +2 -2
  92. package/src/utils/settingsValidation/storage/storageCS.ts +21 -8
  93. package/types/logger/constants.d.ts +3 -3
  94. package/types/sdkFactory/types.d.ts +2 -2
  95. package/types/services/splitApi.d.ts +1 -1
  96. package/types/services/types.d.ts +13 -0
  97. package/types/storages/inMemory/InMemoryStorageCS.d.ts +2 -2
  98. package/types/storages/pluggable/EventsCachePluggable.d.ts +2 -2
  99. package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +2 -2
  100. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +5 -5
  101. package/types/storages/pluggable/SplitsCachePluggable.d.ts +3 -3
  102. package/types/storages/pluggable/constants.d.ts +1 -1
  103. package/types/storages/pluggable/inMemoryWrapper.d.ts +6 -3
  104. package/types/storages/pluggable/index.d.ts +2 -2
  105. package/types/storages/pluggable/wrapperAdapter.d.ts +4 -4
  106. package/types/storages/types.d.ts +21 -22
  107. package/types/sync/streaming/SSEClient/index.d.ts +4 -3
  108. package/types/sync/submitters/submitterManager.d.ts +4 -0
  109. package/types/sync/syncManagerOnline.d.ts +1 -1
  110. package/types/sync/types.d.ts +1 -1
  111. package/types/trackers/impressionObserver/ImpressionObserver.d.ts +1 -1
  112. package/types/types.d.ts +14 -14
  113. package/types/utils/MinEventEmitter.d.ts +6 -6
  114. package/types/utils/constants/index.d.ts +6 -4
  115. package/types/utils/env/isNode.d.ts +4 -0
  116. package/types/utils/settingsValidation/storage/storageCS.d.ts +6 -4
@@ -1,4 +1,4 @@
1
- import { ICustomStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';
1
+ import { IPluggableStorageWrapper, IStorageAsync, IStorageAsyncFactory, IStorageFactoryParams } from '../types';
2
2
 
3
3
  import KeyBuilderSS from '../KeyBuilderSS';
4
4
  import { SplitsCachePluggable } from './SplitsCachePluggable';
@@ -8,14 +8,17 @@ import { EventsCachePluggable } from './EventsCachePluggable';
8
8
  import { wrapperAdapter, METHODS_TO_PROMISE_WRAP } from './wrapperAdapter';
9
9
  import { isObject } from '../../utils/lang';
10
10
  import { validatePrefix } from '../KeyBuilder';
11
- import { STORAGE_CUSTOM } from '../../utils/constants';
11
+ import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
12
+ import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory';
13
+ import EventsCacheInMemory from '../inMemory/EventsCacheInMemory';
14
+ import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory';
12
15
 
13
- const NO_VALID_WRAPPER = 'Expecting custom storage `wrapper` in options, but no valid wrapper instance was provided.';
16
+ const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
14
17
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
15
18
 
16
19
  export interface PluggableStorageOptions {
17
20
  prefix?: string
18
- wrapper: ICustomStorageWrapper
21
+ wrapper: IPluggableStorageWrapper
19
22
  }
20
23
 
21
24
  /**
@@ -32,6 +35,25 @@ function validatePluggableStorageOptions(options: any) {
32
35
  if (missingMethods.length) throw new Error(`${NO_VALID_WRAPPER_INTERFACE} The following methods are missing or invalid: ${missingMethods}`);
33
36
  }
34
37
 
38
+ // subscription to wrapper connect event in order to emit SDK_READY event
39
+ function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) {
40
+ wrapper.connect().then(() => {
41
+ onReadyCb();
42
+ }).catch((e) => {
43
+ onReadyCb(e || new Error('Error connecting wrapper'));
44
+ });
45
+ }
46
+
47
+ // Async return type in `client.track` method on consumer partial mode
48
+ // No need to promisify impressions cache
49
+ function promisifyEventsTrack(events: any) {
50
+ const origTrack = events.track;
51
+ events.track = function () {
52
+ return Promise.resolve(origTrack.apply(this, arguments));
53
+ };
54
+ return events;
55
+ }
56
+
35
57
  /**
36
58
  * Pluggable storage factory for consumer server-side & client-side SplitFactory.
37
59
  */
@@ -41,31 +63,39 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
41
63
 
42
64
  const prefix = validatePrefix(options.prefix);
43
65
 
44
- function PluggableStorageFactory({ log, metadata, onReadyCb }: IStorageFactoryParams): IStorageAsync {
66
+ function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize, optimize }: IStorageFactoryParams): IStorageAsync {
45
67
  const keys = new KeyBuilderSS(prefix, metadata);
46
68
  const wrapper = wrapperAdapter(log, options.wrapper);
69
+ const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
47
70
 
48
- // subscription to Wrapper connect event in order to emit SDK_READY event
49
- wrapper.connect().then(() => {
50
- if (onReadyCb) onReadyCb();
51
- }).catch((e) => {
52
- if (onReadyCb) onReadyCb(e || new Error('Error connecting wrapper'));
53
- });
71
+ // Connects to wrapper and emits SDK_READY event on main client
72
+ wrapperConnect(wrapper, onReadyCb);
54
73
 
55
74
  return {
56
75
  splits: new SplitsCachePluggable(log, keys, wrapper),
57
76
  segments: new SegmentsCachePluggable(log, keys, wrapper),
58
- impressions: new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
59
- events: new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
77
+ impressions: isPartialConsumer ? new ImpressionsCacheInMemory() : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
78
+ impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
79
+ events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
60
80
  // @TODO add telemetry cache when required
61
81
 
62
- // Disconnect the underlying storage, to release its resources (such as open files, database connections, etc).
82
+ // Disconnect the underlying storage
63
83
  destroy() {
64
- return wrapper.close();
84
+ return wrapper.disconnect();
85
+ },
86
+
87
+ // emits SDK_READY event on shared clients and returns a reference to the storage
88
+ shared(_, onReadyCb) {
89
+ wrapperConnect(wrapper, onReadyCb);
90
+ return {
91
+ ...this,
92
+ // no-op destroy, to disconnect the wrapper only when the main client is destroyed
93
+ destroy() { }
94
+ };
65
95
  }
66
96
  };
67
97
  }
68
98
 
69
- PluggableStorageFactory.type = STORAGE_CUSTOM;
99
+ PluggableStorageFactory.type = STORAGE_PLUGGABLE;
70
100
  return PluggableStorageFactory;
71
101
  }
@@ -1,5 +1,5 @@
1
1
  import { ILogger } from '../../logger/types';
2
- import { ICustomStorageWrapper } from '../types';
2
+ import { IPluggableStorageWrapper } from '../types';
3
3
  import { LOG_PREFIX } from './constants';
4
4
 
5
5
  export const METHODS_TO_PROMISE_WRAP: string[] = [
@@ -19,18 +19,18 @@ export const METHODS_TO_PROMISE_WRAP: string[] = [
19
19
  'removeItems',
20
20
  'getItems',
21
21
  'connect',
22
- 'close'
22
+ 'disconnect'
23
23
  ];
24
24
 
25
25
  /**
26
- * Adapter of the Custom Storage Wrapper.
26
+ * Adapter of the Pluggable Storage Wrapper.
27
27
  * Used to handle exceptions as rejected promises, in order to simplify the error handling on storages.
28
28
  *
29
29
  * @param log logger instance
30
- * @param wrapper custom storage wrapper to adapt
30
+ * @param wrapper storage wrapper to adapt
31
31
  * @returns an adapted version of the given storage wrapper
32
32
  */
33
- export function wrapperAdapter(log: ILogger, wrapper: ICustomStorageWrapper): ICustomStorageWrapper {
33
+ export function wrapperAdapter(log: ILogger, wrapper: IPluggableStorageWrapper): IPluggableStorageWrapper {
34
34
 
35
35
  const wrapperAdapter: Record<string, Function> = {};
36
36
 
@@ -1,12 +1,12 @@
1
1
  import { MaybeThenable, IMetadata, ISplitFiltersValidation } from '../dtos/types';
2
2
  import { ILogger } from '../logger/types';
3
3
  import { StoredEventWithMetadata, StoredImpressionWithMetadata } from '../sync/submitters/types';
4
- import { SplitIO, ImpressionDTO } from '../types';
4
+ import { SplitIO, ImpressionDTO, SDKMode } from '../types';
5
5
 
6
6
  /**
7
- * Interface of a custom wrapper storage.
7
+ * Interface of a pluggable storage wrapper.
8
8
  */
9
- export interface ICustomStorageWrapper {
9
+ export interface IPluggableStorageWrapper {
10
10
 
11
11
  /** Key-Value operations */
12
12
 
@@ -28,7 +28,7 @@ export interface ICustomStorageWrapper {
28
28
  * @returns {Promise<void>} A promise that resolves if the operation success, whether the key was added or updated.
29
29
  * The promise rejects if the operation fails.
30
30
  */
31
- set: (key: string, value: string) => Promise<void | boolean>
31
+ set: (key: string, value: string) => Promise<boolean | void>
32
32
  /**
33
33
  * Add or update an item with a specified `key` and `value`.
34
34
  *
@@ -47,7 +47,7 @@ export interface ICustomStorageWrapper {
47
47
  * @returns {Promise<void>} A promise that resolves if the operation success, whether the key existed and was removed or it didn't exist.
48
48
  * The promise rejects if the operation fails, for example, if there is a connection error.
49
49
  */
50
- del: (key: string) => Promise<void | boolean>
50
+ del: (key: string) => Promise<boolean | void>
51
51
  /**
52
52
  * Returns all keys matching the given prefix.
53
53
  *
@@ -140,20 +140,20 @@ export interface ICustomStorageWrapper {
140
140
  * @function addItems
141
141
  * @param {string} key Set key
142
142
  * @param {string} items Items to add
143
- * @returns {Promise<void>} A promise that resolves if the operation success.
143
+ * @returns {Promise<boolean | void>} A promise that resolves if the operation success.
144
144
  * The promise rejects if the operation fails, for example, if there is a connection error or the key holds a value that is not a set.
145
145
  */
146
- addItems: (key: string, items: string[]) => Promise<void>
146
+ addItems: (key: string, items: string[]) => Promise<boolean | void>
147
147
  /**
148
148
  * Remove the specified `items` from the set stored at `key`. Those items that are not part of the set are ignored.
149
149
  *
150
150
  * @function removeItems
151
151
  * @param {string} key Set key
152
152
  * @param {string} items Items to remove
153
- * @returns {Promise<void>} A promise that resolves if the operation success. If key does not exist, the promise also resolves.
153
+ * @returns {Promise<boolean | void>} A promise that resolves if the operation success. If key does not exist, the promise also resolves.
154
154
  * The promise rejects if the operation fails, for example, if there is a connection error or the key holds a value that is not a set.
155
155
  */
156
- removeItems: (key: string, items: string[]) => Promise<void>
156
+ removeItems: (key: string, items: string[]) => Promise<boolean | void>
157
157
  /**
158
158
  * Returns all the items of the `key` set.
159
159
  *
@@ -169,7 +169,7 @@ export interface ICustomStorageWrapper {
169
169
  /**
170
170
  * Connects to the underlying storage.
171
171
  * It is meant for storages that requires to be connected to some database or server. Otherwise it can just return a resolved promise.
172
- * Note: will be called once on SplitFactory instantiation.
172
+ * Note: will be called once on SplitFactory instantiation and once per each shared client instantiation.
173
173
  *
174
174
  * @function connect
175
175
  * @returns {Promise<void>} A promise that resolves when the wrapper successfully connect to the underlying storage.
@@ -177,15 +177,15 @@ export interface ICustomStorageWrapper {
177
177
  */
178
178
  connect: () => Promise<void>
179
179
  /**
180
- * Disconnects the underlying storage.
180
+ * Disconnects from the underlying storage.
181
181
  * It is meant for storages that requires to be closed, in order to release resources. Otherwise it can just return a resolved promise.
182
- * Note: will be called once on SplitFactory client destroy.
182
+ * Note: will be called once on SplitFactory main client destroy.
183
183
  *
184
- * @function close
184
+ * @function disconnect
185
185
  * @returns {Promise<void>} A promise that resolves when the operation ends.
186
186
  * The promise never rejects.
187
187
  */
188
- close: () => Promise<void>
188
+ disconnect: () => Promise<void>
189
189
  }
190
190
 
191
191
  /** Splits cache */
@@ -271,7 +271,7 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase {
271
271
  export interface ISegmentsCacheAsync extends ISegmentsCacheBase {
272
272
  addToSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
273
273
  removeFromSegment(name: string, segmentKeys: string[]): Promise<boolean | void>
274
- isInSegment(name: string, key?: string): Promise<boolean>
274
+ isInSegment(name: string, key: string): Promise<boolean>
275
275
  registerSegments(names: string[]): Promise<boolean | void>
276
276
  getRegisteredSegments(): Promise<string[]>
277
277
  setChangeNumber(name: string, changeNumber: number): Promise<boolean | void>
@@ -395,7 +395,8 @@ export interface IStorageBase<
395
395
  events: TEventsCache,
396
396
  latencies?: TLatenciesCache,
397
397
  counts?: TCountsCache,
398
- destroy(): void,
398
+ destroy(): void | Promise<void>,
399
+ shared?: (matchingKey: string, onReadyCb: (error?: any) => void) => this
399
400
  }
400
401
 
401
402
  export type IStorageSync = IStorageBase<
@@ -407,15 +408,11 @@ export type IStorageSync = IStorageBase<
407
408
  ICountsCacheSync
408
409
  >
409
410
 
410
- export interface IStorageSyncCS extends IStorageSync {
411
- shared(matchingKey: string): IStorageSync
412
- }
413
-
414
411
  export type IStorageAsync = IStorageBase<
415
412
  ISplitsCacheAsync,
416
413
  ISegmentsCacheAsync,
417
- IImpressionsCacheAsync,
418
- IEventsCacheAsync,
414
+ IImpressionsCacheAsync | IImpressionsCacheSync,
415
+ IEventsCacheAsync | IEventsCacheSync,
419
416
  ILatenciesCacheAsync,
420
417
  ICountsCacheAsync
421
418
  >
@@ -433,14 +430,17 @@ export interface IStorageFactoryParams {
433
430
  matchingKey?: string, /* undefined on server-side SDKs */
434
431
  splitFiltersValidation?: ISplitFiltersValidation,
435
432
 
433
+ // ATM, only used by PluggableStorage
434
+ mode?: SDKMode,
435
+
436
436
  // This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed,
437
437
  // it means that the storge fail to connect and shouldn't be used.
438
438
  // It is meant for emitting SDK_READY event in consumer mode, and for synchronizer to wait before using the storage.
439
- onReadyCb?: (error?: any) => void,
439
+ onReadyCb: (error?: any) => void,
440
440
  metadata: IMetadata,
441
441
  }
442
442
 
443
- export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'CUSTOM';
443
+ export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE';
444
444
 
445
445
  export type IStorageSyncFactory = {
446
446
  type: StorageType,
@@ -38,7 +38,7 @@ export function mySegmentsUpdaterFactory(
38
38
  // mySegmentsPromise = tracker.start(tracker.TaskNames.MY_SEGMENTS_FETCH, startingUp ? metricCollectors : false, mySegmentsPromise);
39
39
  }
40
40
 
41
- // @TODO if allowing custom storages, handle async execution
41
+ // @TODO if allowing pluggable storages, handle async execution
42
42
  function updateSegments(segmentsData: SegmentsData) {
43
43
 
44
44
  let shouldNotifyUpdate;
@@ -1,3 +1,4 @@
1
+ import { IEventSourceConstructor } from '../../../services/types';
1
2
  import { ISettings } from '../../../types';
2
3
  import { IAuthTokenPushEnabled } from '../AuthClient/types';
3
4
  import { ISSEClient, ISseEventHandler } from './types';
@@ -31,9 +32,9 @@ function buildSSEHeaders(settings: ISettings) {
31
32
  */
32
33
  export default class SSEClient implements ISSEClient {
33
34
  // Instance properties:
34
- eventSource: typeof EventSource;
35
+ eventSource?: IEventSourceConstructor;
35
36
  streamingUrl: string;
36
- connection?: InstanceType<typeof EventSource>;
37
+ connection?: InstanceType<IEventSourceConstructor>;
37
38
  handler?: ISseEventHandler;
38
39
  useHeaders?: boolean;
39
40
  headers: Record<string, string>;
@@ -46,8 +47,7 @@ export default class SSEClient implements ISSEClient {
46
47
  * @param getEventSource Function to get the EventSource constructor.
47
48
  * @throws 'EventSource API is not available. ' if EventSource is not available.
48
49
  */
49
- constructor(settings: ISettings, useHeaders?: boolean, getEventSource?: () => (typeof EventSource | undefined)) {
50
- // @ts-expect-error
50
+ constructor(settings: ISettings, useHeaders?: boolean, getEventSource?: () => (IEventSourceConstructor | undefined)) {
51
51
  this.eventSource = getEventSource && getEventSource();
52
52
  // if eventSource is not available, throw an exception
53
53
  if (!this.eventSource) throw new Error('EventSource API is not available. ');
@@ -79,7 +79,7 @@ export default class SSEClient implements ISSEClient {
79
79
  ).join(',');
80
80
  const url = `${this.streamingUrl}?channels=${channelsQueryParam}&accessToken=${authToken.token}&v=${VERSION}&heartbeats=true`; // same results using `&heartbeats=false`
81
81
 
82
- this.connection = new this.eventSource(
82
+ this.connection = new this.eventSource!(
83
83
  // For client-side SDKs, SplitSDKClientKey and SplitSDKClientKey metadata is passed as query params,
84
84
  // because native EventSource implementations for browser doesn't support headers.
85
85
  this.useHeaders ? url : url + `&SplitSDKVersion=${this.headers.SplitSDKVersion}&SplitSDKClientKey=${this.headers.SplitSDKClientKey}`,
@@ -83,7 +83,6 @@ export default class SplitsUpdateWorker implements IUpdateWorker {
83
83
  * @param {string} defaultTreatment default treatment value
84
84
  */
85
85
  killSplit({ changeNumber, splitName, defaultTreatment }: ISplitKillData) {
86
- // @TODO handle retry due to errors in storage, once we allow the definition of custom async storages
87
86
  if (this.splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
88
87
  // trigger an SDK_UPDATE if Split was killed locally
89
88
  this.splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true);
@@ -0,0 +1,22 @@
1
+ import { syncTaskComposite } from '../syncTaskComposite';
2
+ import { eventsSyncTaskFactory } from './eventsSyncTask';
3
+ import { impressionsSyncTaskFactory } from './impressionsSyncTask';
4
+ import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask';
5
+ import { ISplitApi } from '../../services/types';
6
+ import { IStorageSync } from '../../storages/types';
7
+ import { ISettings } from '../../types';
8
+
9
+ export function submitterManagerFactory(
10
+ settings: ISettings,
11
+ storage: IStorageSync,
12
+ splitApi: ISplitApi,
13
+ ) {
14
+ const log = settings.log;
15
+ const submitters = [
16
+ impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
17
+ eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow)
18
+ // @TODO add telemetry submitter
19
+ ];
20
+ if (storage.impressionCounts) submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts));
21
+ return syncTaskComposite(submitters);
22
+ }
@@ -1,8 +1,5 @@
1
- import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from './types';
2
- import { syncTaskComposite } from './syncTaskComposite';
3
- import { eventsSyncTaskFactory } from './submitters/eventsSyncTask';
4
- import { impressionsSyncTaskFactory } from './submitters/impressionsSyncTask';
5
- import { impressionCountsSyncTaskFactory } from './submitters/impressionCountsSyncTask';
1
+ import { ISyncManagerCS, ISyncManagerFactoryParams } from './types';
2
+ import { submitterManagerFactory } from './submitters/submitterManager';
6
3
  import { IReadinessManager } from '../readiness/types';
7
4
  import { IStorageSync } from '../storages/types';
8
5
  import { IPushManagerFactoryParams, IPushManager, IPushManagerCS } from './streaming/types';
@@ -19,7 +16,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
19
16
  * @param pushManagerFactory optional to build a SyncManager with or without streaming support
20
17
  */
21
18
  export function syncManagerOnlineFactory(
22
- pollingManagerFactory: (...args: IPollingManagerFactoryParams) => IPollingManager,
19
+ pollingManagerFactory?: (...args: IPollingManagerFactoryParams) => IPollingManager,
23
20
  pushManagerFactory?: (...args: IPushManagerFactoryParams) => IPushManager | undefined
24
21
  ): (params: ISyncManagerFactoryParams) => ISyncManagerCS {
25
22
 
@@ -37,30 +34,24 @@ export function syncManagerOnlineFactory(
37
34
  const log = settings.log;
38
35
 
39
36
  /** Polling Manager */
40
- const pollingManager = pollingManagerFactory(splitApi, storage, readiness, settings);
37
+ const pollingManager = pollingManagerFactory && pollingManagerFactory(splitApi, storage, readiness, settings);
41
38
 
42
39
  /** Push Manager */
43
- const pushManager = settings.streamingEnabled && pushManagerFactory ?
40
+ const pushManager = settings.streamingEnabled && pollingManager && pushManagerFactory ?
44
41
  pushManagerFactory(pollingManager, storage, readiness, splitApi.fetchAuth, platform, settings) :
45
42
  undefined;
46
43
 
47
44
  /** Submitter Manager */
48
- // 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
49
- const submitters = [
50
- impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
51
- eventsSyncTaskFactory(log, splitApi.postEventsBulk, storage.events, settings.scheduler.eventsPushRate, settings.startup.eventsFirstPushWindow)
52
- // @TODO add telemetry submitter
53
- ];
54
- if (storage.impressionCounts) submitters.push(impressionCountsSyncTaskFactory(log, splitApi.postTestImpressionsCount, storage.impressionCounts));
55
- const submitter = syncTaskComposite(submitters);
45
+ // It is not inyected as push and polling managers, because at the moment it is required
46
+ const submitter = submitterManagerFactory(settings, storage, splitApi);
56
47
 
57
48
 
58
49
  /** Sync Manager logic */
59
50
 
60
51
  function startPolling() {
61
- if (!pollingManager.isRunning()) {
52
+ if (!pollingManager!.isRunning()) {
62
53
  log.info(SYNC_START_POLLING);
63
- pollingManager.start();
54
+ pollingManager!.start();
64
55
  } else {
65
56
  log.info(SYNC_CONTINUE_POLLING);
66
57
  }
@@ -69,10 +60,10 @@ export function syncManagerOnlineFactory(
69
60
  function stopPollingAndSyncAll() {
70
61
  log.info(SYNC_STOP_POLLING);
71
62
  // if polling, stop
72
- if (pollingManager.isRunning()) pollingManager.stop();
63
+ if (pollingManager!.isRunning()) pollingManager!.stop();
73
64
 
74
65
  // fetch splits and segments. There is no need to catch this promise (it is always resolved)
75
- pollingManager.syncAll();
66
+ pollingManager!.syncAll();
76
67
  }
77
68
 
78
69
  if (pushManager) {
@@ -91,15 +82,17 @@ export function syncManagerOnlineFactory(
91
82
  */
92
83
  start() {
93
84
  // start syncing splits and segments
94
- if (pushManager) {
95
- // Doesn't call `syncAll` when the syncManager is resuming
96
- if (startFirstTime) {
97
- pollingManager.syncAll();
98
- startFirstTime = false;
85
+ if (pollingManager) {
86
+ if (pushManager) {
87
+ // Doesn't call `syncAll` when the syncManager is resuming
88
+ if (startFirstTime) {
89
+ pollingManager.syncAll();
90
+ startFirstTime = false;
91
+ }
92
+ pushManager.start();
93
+ } else {
94
+ pollingManager.start();
99
95
  }
100
- pushManager.start();
101
- } else {
102
- pollingManager.start();
103
96
  }
104
97
 
105
98
  // start periodic data recording (events, impressions, telemetry).
@@ -113,7 +106,7 @@ export function syncManagerOnlineFactory(
113
106
  stop() {
114
107
  // stop syncing
115
108
  if (pushManager) pushManager.stop();
116
- if (pollingManager.isRunning()) pollingManager.stop();
109
+ if (pollingManager && pollingManager.isRunning()) pollingManager.stop();
117
110
 
118
111
  // stop periodic data recording (events, impressions, telemetry).
119
112
  if (submitter) submitter.stop();
@@ -130,8 +123,9 @@ export function syncManagerOnlineFactory(
130
123
  },
131
124
 
132
125
  // [Only used for client-side]
133
- // It assumes that polling and push managers implement the interfaces for client-side
134
- shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager {
126
+ // If polling and push managers are defined (standalone mode), they implement the interfaces for client-side
127
+ shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync) {
128
+ if (!pollingManager) return;
135
129
 
136
130
  const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).add(matchingKey, readinessManager, storage);
137
131
 
@@ -139,7 +133,7 @@ export function syncManagerOnlineFactory(
139
133
  isRunning: mySegmentsSyncTask.isRunning,
140
134
  start() {
141
135
  if (pushManager) {
142
- if (pollingManager.isRunning()) {
136
+ if (pollingManager!.isRunning()) {
143
137
  // if doing polling, we must start the periodic fetch of data
144
138
  if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
145
139
  } else {
package/src/sync/types.ts CHANGED
@@ -47,7 +47,7 @@ export interface ISyncManager extends ITask {
47
47
  }
48
48
 
49
49
  export interface ISyncManagerCS extends ISyncManager {
50
- shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager
50
+ shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync): ISyncManager | undefined
51
51
  }
52
52
 
53
53
  export interface ISyncManagerFactoryParams {
@@ -2,7 +2,7 @@ import { ImpressionDTO } from '../../types';
2
2
  import LRUCache from '../../utils/LRUCache';
3
3
  import { IImpressionObserver } from './types';
4
4
 
5
- export default class ImpressionObserver<K extends string | number> implements IImpressionObserver {
5
+ export default class ImpressionObserver<K extends string | number = string> implements IImpressionObserver {
6
6
  private cache: LRUCache<K, number>;
7
7
  private hasher: (impression: ImpressionDTO) => K;
8
8
 
@@ -1,11 +1,11 @@
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
  import { ISettings } from '../../types';
3
3
 
4
4
  /**
5
5
  * Checks if impressions previous time should be added or not.
6
6
  */
7
7
  export function shouldAddPt(settings: ISettings) {
8
- return [PRODUCER_MODE, STANDALONE_MODE].indexOf(settings.mode) > -1 ? true : false;
8
+ return [PRODUCER_MODE, STANDALONE_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) > -1 ? true : false;
9
9
  }
10
10
 
11
11
  /**
package/src/types.ts CHANGED
@@ -3,19 +3,20 @@ import { IIntegration, IIntegrationFactoryParams } from './integrations/types';
3
3
  import { ILogger } from './logger/types';
4
4
  /* eslint-disable no-use-before-define */
5
5
 
6
- import { IStorageFactoryParams, IStorageSyncCS, IStorageSync, IStorageAsync, IStorageSyncFactory } from './storages/types';
6
+ import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory, IStorageAsyncFactory } from './storages/types';
7
7
  import { ISyncManagerFactoryParams, ISyncManagerCS } from './sync/types';
8
8
 
9
9
  /**
10
- * EventEmitter interface with the minimal methods used by the SDK
10
+ * Reduced version of NodeJS.EventEmitter interface with the minimal methods used by the SDK
11
+ * @see {@link https://nodejs.org/api/events.html}
11
12
  */
12
- export interface IEventEmitter extends Pick<NodeJS.EventEmitter, 'addListener' | 'on' | 'once' | 'removeListener' | 'off' | 'removeAllListeners' | 'emit'> {
13
- addListener(event: string, listener: (...args: any[]) => void): any;
14
- on(event: string, listener: (...args: any[]) => void): any
15
- once(event: string, listener: (...args: any[]) => void): any
16
- removeListener(event: string, listener: (...args: any[]) => void): any;
17
- off(event: string, listener: (...args: any[]) => void): any;
18
- removeAllListeners(event?: string): any
13
+ export interface IEventEmitter {
14
+ addListener(event: string, listener: (...args: any[]) => void): this;
15
+ on(event: string, listener: (...args: any[]) => void): this
16
+ once(event: string, listener: (...args: any[]) => void): this
17
+ removeListener(event: string, listener: (...args: any[]) => void): this;
18
+ off(event: string, listener: (...args: any[]) => void): this;
19
+ removeAllListeners(event?: string): this
19
20
  emit(event: string, ...args: any[]): boolean
20
21
  }
21
22
 
@@ -52,7 +53,7 @@ type EventConsts = {
52
53
  * SDK Modes.
53
54
  * @typedef {string} SDKMode
54
55
  */
55
- export type SDKMode = 'standalone' | 'consumer' | 'localhost';
56
+ export type SDKMode = 'standalone' | 'consumer' | 'localhost' | 'consumer_partial';
56
57
  /**
57
58
  * Settings interface. This is a representation of the settings the SDK expose, that's why
58
59
  * most of it's props are readonly. Only features should be rewritten when localhost mode is active.
@@ -85,7 +86,7 @@ export interface ISettings {
85
86
  retriesOnFailureBeforeReady: number,
86
87
  eventsFirstPushWindow: number
87
88
  },
88
- readonly storage: IStorageSyncFactory,
89
+ readonly storage: IStorageSyncFactory | IStorageAsyncFactory,
89
90
  readonly integrations?: Array<(params: IIntegrationFactoryParams) => IIntegration | void>,
90
91
  readonly urls: {
91
92
  events: string,
@@ -93,7 +94,7 @@ export interface ISettings {
93
94
  auth: string,
94
95
  streaming: string
95
96
  },
96
- readonly debug: boolean | LogLevel,
97
+ readonly debug: boolean | LogLevel | ILogger,
97
98
  readonly version: string,
98
99
  features: SplitIO.MockedFeaturesFilePath | SplitIO.MockedFeaturesMap,
99
100
  readonly streamingEnabled: boolean,
@@ -841,7 +842,7 @@ export namespace SplitIO {
841
842
  * Defines which kind of storage we should instanciate.
842
843
  * @property {Object} storage
843
844
  */
844
- storage?: (params: IStorageFactoryParams) => IStorageSyncCS,
845
+ storage?: (params: IStorageFactoryParams) => IStorageSync | IStorageAsync,
845
846
  /**
846
847
  * List of URLs that the SDK will use as base for it's synchronization functionalities, applicable only when running as standalone.
847
848
  * Do not change these settings unless you're working an advanced use case, like connecting to the Split proxy.
@@ -18,7 +18,7 @@ export default class EventEmitter implements IEventEmitter {
18
18
  boolean // whether it is a one-time listener or not
19
19
  ]>> = {};
20
20
 
21
- private registerListener(type: string, listener: (...args: any[]) => void, oneTime: boolean): EventEmitter {
21
+ private registerListener(type: string, listener: (...args: any[]) => void, oneTime: boolean) {
22
22
  checkListener(listener);
23
23
 
24
24
  // To avoid recursion in the case that type === "newListener" before
@@ -33,27 +33,27 @@ export default class EventEmitter implements IEventEmitter {
33
33
  return this;
34
34
  }
35
35
 
36
- addListener(type: string, listener: (...args: any[]) => void): EventEmitter {
36
+ addListener(type: string, listener: (...args: any[]) => void) {
37
37
  return this.registerListener(type, listener, false);
38
38
  }
39
39
 
40
40
  // alias of addListener
41
- on(type: string, listener: (...args: any[]) => void): EventEmitter {
41
+ on(type: string, listener: (...args: any[]) => void) {
42
42
  return this.addListener(type, listener);
43
43
  }
44
44
 
45
- once(type: string, listener: (...args: any[]) => void): EventEmitter {
45
+ once(type: string, listener: (...args: any[]) => void) {
46
46
  return this.registerListener(type, listener, true);
47
47
  }
48
48
 
49
- // eslint-disable-next-line
50
- removeListener(type: string, listener: (...args: any[]) => void): EventEmitter {
49
+ // @ts-ignore
50
+ removeListener(/* type: string, listener: (...args: any[]) => void */) {
51
51
  throw new Error('Method not implemented.');
52
52
  }
53
53
 
54
- // alias of removeListener
55
- off(type: string, listener: (...args: any[]) => void): EventEmitter {
56
- return this.removeListener(type, listener);
54
+ // @ts-ignore alias of removeListener
55
+ off(/* type: string, listener: (...args: any[]) => void */) {
56
+ return this.removeListener(/* type, listener */);
57
57
  }
58
58
 
59
59
  emit(type: string, ...args: any[]): boolean {
@@ -68,7 +68,7 @@ export default class EventEmitter implements IEventEmitter {
68
68
  return true;
69
69
  }
70
70
 
71
- removeAllListeners(type?: string): EventEmitter {
71
+ removeAllListeners(type?: string) {
72
72
  if (!this.listeners[REMOVE_LISTENER_EVENT]) {
73
73
  // if not listening for `removeListener`, no need to emit
74
74
  if (type) {