@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.
- package/README.md +1 -1
- package/cjs/listeners/browser.js +3 -1
- package/cjs/logger/constants.js +4 -4
- package/cjs/logger/messages/error.js +2 -1
- package/cjs/logger/messages/warn.js +0 -1
- package/cjs/sdkClient/sdkClient.js +4 -4
- package/cjs/sdkClient/sdkClientMethodCS.js +16 -5
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +17 -6
- package/cjs/sdkFactory/index.js +6 -3
- package/cjs/storages/inMemory/InMemoryStorage.js +0 -3
- package/cjs/storages/inRedis/index.js +1 -2
- package/cjs/storages/pluggable/SplitsCachePluggable.js +1 -1
- package/cjs/storages/pluggable/constants.js +1 -1
- package/cjs/storages/pluggable/inMemoryWrapper.js +19 -5
- package/cjs/storages/pluggable/index.js +38 -15
- package/cjs/storages/pluggable/wrapperAdapter.js +3 -3
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +1 -1
- package/cjs/sync/streaming/SSEClient/index.js +0 -1
- package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
- package/cjs/sync/submitters/submitterManager.js +19 -0
- package/cjs/sync/syncManagerOnline.js +20 -26
- package/cjs/trackers/impressionObserver/utils.js +1 -1
- package/cjs/utils/MinEventEmitter.js +5 -5
- package/cjs/utils/constants/index.js +3 -2
- package/cjs/utils/env/isNode.js +4 -1
- package/cjs/utils/settingsValidation/mode.js +1 -1
- package/cjs/utils/settingsValidation/storage/storageCS.js +17 -3
- package/esm/listeners/browser.js +3 -1
- package/esm/logger/constants.js +3 -3
- package/esm/logger/messages/error.js +2 -1
- package/esm/logger/messages/warn.js +0 -1
- package/esm/sdkClient/sdkClient.js +5 -5
- package/esm/sdkClient/sdkClientMethodCS.js +16 -5
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +17 -6
- package/esm/sdkFactory/index.js +6 -3
- package/esm/storages/inMemory/InMemoryStorage.js +0 -3
- package/esm/storages/inRedis/index.js +1 -2
- package/esm/storages/pluggable/SplitsCachePluggable.js +1 -1
- package/esm/storages/pluggable/constants.js +1 -1
- package/esm/storages/pluggable/inMemoryWrapper.js +19 -5
- package/esm/storages/pluggable/index.js +40 -16
- package/esm/storages/pluggable/wrapperAdapter.js +3 -3
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
- package/esm/sync/streaming/SSEClient/index.js +0 -1
- package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +0 -1
- package/esm/sync/submitters/submitterManager.js +15 -0
- package/esm/sync/syncManagerOnline.js +20 -26
- package/esm/trackers/impressionObserver/utils.js +2 -2
- package/esm/utils/MinEventEmitter.js +5 -5
- package/esm/utils/constants/index.js +2 -1
- package/esm/utils/env/isNode.js +4 -1
- package/esm/utils/settingsValidation/mode.js +2 -2
- package/esm/utils/settingsValidation/storage/storageCS.js +19 -5
- package/package.json +3 -4
- package/src/listeners/browser.ts +3 -1
- package/src/logger/constants.ts +3 -3
- package/src/logger/messages/error.ts +2 -1
- package/src/logger/messages/warn.ts +0 -1
- package/src/sdkClient/sdkClient.ts +6 -6
- package/src/sdkClient/sdkClientMethodCS.ts +14 -4
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +15 -5
- package/src/sdkFactory/index.ts +7 -3
- package/src/sdkFactory/types.ts +2 -2
- package/src/services/splitApi.ts +4 -1
- package/src/services/types.ts +16 -2
- package/src/storages/inLocalStorage/index.ts +2 -2
- package/src/storages/inMemory/InMemoryStorage.ts +0 -3
- package/src/storages/inMemory/InMemoryStorageCS.ts +2 -2
- package/src/storages/inRedis/index.ts +1 -1
- package/src/storages/pluggable/EventsCachePluggable.ts +3 -3
- package/src/storages/pluggable/ImpressionsCachePluggable.ts +3 -3
- package/src/storages/pluggable/SegmentsCachePluggable.ts +3 -3
- package/src/storages/pluggable/SplitsCachePluggable.ts +4 -4
- package/src/storages/pluggable/constants.ts +1 -1
- package/src/storages/pluggable/inMemoryWrapper.ts +20 -6
- package/src/storages/pluggable/index.ts +46 -16
- package/src/storages/pluggable/wrapperAdapter.ts +5 -5
- package/src/storages/types.ts +24 -24
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
- package/src/sync/streaming/SSEClient/index.ts +5 -5
- package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +0 -1
- package/src/sync/submitters/submitterManager.ts +22 -0
- package/src/sync/syncManagerOnline.ts +26 -32
- package/src/sync/types.ts +1 -1
- package/src/trackers/impressionObserver/ImpressionObserver.ts +1 -1
- package/src/trackers/impressionObserver/utils.ts +2 -2
- package/src/types.ts +14 -13
- package/src/utils/MinEventEmitter.ts +10 -10
- package/src/utils/constants/index.ts +6 -4
- package/src/utils/env/isNode.ts +4 -1
- package/src/utils/settingsValidation/mode.ts +2 -2
- package/src/utils/settingsValidation/storage/storageCS.ts +21 -8
- package/types/logger/constants.d.ts +3 -3
- package/types/sdkFactory/types.d.ts +2 -2
- package/types/services/splitApi.d.ts +1 -1
- package/types/services/types.d.ts +13 -0
- package/types/storages/inMemory/InMemoryStorageCS.d.ts +2 -2
- package/types/storages/pluggable/EventsCachePluggable.d.ts +2 -2
- package/types/storages/pluggable/ImpressionsCachePluggable.d.ts +2 -2
- package/types/storages/pluggable/SegmentsCachePluggable.d.ts +5 -5
- package/types/storages/pluggable/SplitsCachePluggable.d.ts +3 -3
- package/types/storages/pluggable/constants.d.ts +1 -1
- package/types/storages/pluggable/inMemoryWrapper.d.ts +6 -3
- package/types/storages/pluggable/index.d.ts +2 -2
- package/types/storages/pluggable/wrapperAdapter.d.ts +4 -4
- package/types/storages/types.d.ts +21 -22
- package/types/sync/streaming/SSEClient/index.d.ts +4 -3
- package/types/sync/submitters/submitterManager.d.ts +4 -0
- package/types/sync/syncManagerOnline.d.ts +1 -1
- package/types/sync/types.d.ts +1 -1
- package/types/trackers/impressionObserver/ImpressionObserver.d.ts +1 -1
- package/types/types.d.ts +14 -14
- package/types/utils/MinEventEmitter.d.ts +6 -6
- package/types/utils/constants/index.d.ts +6 -4
- package/types/utils/env/isNode.d.ts +4 -0
- package/types/utils/settingsValidation/storage/storageCS.d.ts +6 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
-
//
|
|
49
|
-
wrapper
|
|
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
|
-
|
|
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
|
|
82
|
+
// Disconnect the underlying storage
|
|
63
83
|
destroy() {
|
|
64
|
-
return wrapper.
|
|
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 =
|
|
99
|
+
PluggableStorageFactory.type = STORAGE_PLUGGABLE;
|
|
70
100
|
return PluggableStorageFactory;
|
|
71
101
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ILogger } from '../../logger/types';
|
|
2
|
-
import {
|
|
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
|
-
'
|
|
22
|
+
'disconnect'
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Adapter of the
|
|
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
|
|
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:
|
|
33
|
+
export function wrapperAdapter(log: ILogger, wrapper: IPluggableStorageWrapper): IPluggableStorageWrapper {
|
|
34
34
|
|
|
35
35
|
const wrapperAdapter: Record<string, Function> = {};
|
|
36
36
|
|
package/src/storages/types.ts
CHANGED
|
@@ -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
|
|
7
|
+
* Interface of a pluggable storage wrapper.
|
|
8
8
|
*/
|
|
9
|
-
export interface
|
|
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<
|
|
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<
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
439
|
+
onReadyCb: (error?: any) => void,
|
|
440
440
|
metadata: IMetadata,
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
export type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | '
|
|
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
|
|
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
|
|
35
|
+
eventSource?: IEventSourceConstructor;
|
|
35
36
|
streamingUrl: string;
|
|
36
|
-
connection?: InstanceType<
|
|
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?: () => (
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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
|
|
49
|
-
const
|
|
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
|
|
52
|
+
if (!pollingManager!.isRunning()) {
|
|
62
53
|
log.info(SYNC_START_POLLING);
|
|
63
|
-
pollingManager
|
|
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
|
|
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
|
|
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 (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
//
|
|
134
|
-
shared(matchingKey: string, readinessManager: IReadinessManager, storage: IStorageSync)
|
|
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
|
|
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,
|
|
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
|
|
13
|
-
addListener(event: string, listener: (...args: any[]) => void):
|
|
14
|
-
on(event: string, listener: (...args: any[]) => void):
|
|
15
|
-
once(event: string, listener: (...args: any[]) => void):
|
|
16
|
-
removeListener(event: string, listener: (...args: any[]) => void):
|
|
17
|
-
off(event: string, listener: (...args: any[]) => void):
|
|
18
|
-
removeAllListeners(event?: string):
|
|
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) =>
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
45
|
+
once(type: string, listener: (...args: any[]) => void) {
|
|
46
46
|
return this.registerListener(type, listener, true);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
removeListener(type: string, listener: (...args: any[]) => void)
|
|
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)
|
|
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)
|
|
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) {
|