@splitsoftware/splitio-commons 1.0.1-rc.0 → 1.0.1-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/logger/constants.js +4 -4
- package/cjs/logger/messages/error.js +2 -1
- package/cjs/logger/messages/warn.js +0 -1
- package/cjs/sdkFactory/index.js +1 -1
- 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 +3 -3
- package/cjs/storages/pluggable/index.js +9 -7
- 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/trackers/impressionObserver/utils.js +1 -1
- package/cjs/utils/MinEventEmitter.js +5 -5
- package/cjs/utils/constants/index.js +2 -2
- package/cjs/utils/env/isNode.js +4 -1
- package/cjs/utils/settingsValidation/storage/storageCS.js +11 -8
- 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/sdkFactory/index.js +1 -1
- 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 +3 -3
- package/esm/storages/pluggable/index.js +10 -8
- 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/trackers/impressionObserver/utils.js +2 -2
- package/esm/utils/MinEventEmitter.js +5 -5
- package/esm/utils/constants/index.js +1 -1
- package/esm/utils/env/isNode.js +4 -1
- package/esm/utils/settingsValidation/storage/storageCS.js +13 -10
- package/package.json +3 -4
- 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/sdkFactory/index.ts +1 -1
- package/src/sdkFactory/types.ts +2 -2
- package/src/services/splitApi.ts +4 -1
- package/src/services/types.ts +16 -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 +5 -5
- package/src/storages/pluggable/index.ts +13 -11
- package/src/storages/pluggable/wrapperAdapter.ts +5 -5
- package/src/storages/types.ts +7 -7
- 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/trackers/impressionObserver/ImpressionObserver.ts +1 -1
- package/src/trackers/impressionObserver/utils.ts +2 -2
- package/src/types.ts +10 -9
- package/src/utils/MinEventEmitter.ts +10 -10
- package/src/utils/constants/index.ts +1 -1
- package/src/utils/env/isNode.ts +4 -1
- package/src/utils/settingsValidation/storage/storageCS.ts +12 -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 +11 -0
- 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 +2 -2
- 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 +3 -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 +6 -6
- package/types/sync/streaming/SSEClient/index.d.ts +4 -3
- package/types/trackers/impressionObserver/ImpressionObserver.d.ts +1 -1
- package/types/types.d.ts +10 -10
- package/types/utils/MinEventEmitter.d.ts +6 -6
- package/types/utils/constants/index.d.ts +1 -1
- package/types/utils/env/isNode.d.ts +4 -0
- package/types/utils/settingsValidation/storage/storageCS.d.ts +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InMemoryStorageCSFactory } from '../../../storages/inMemory/InMemoryStorageCS';
|
|
2
|
-
import {
|
|
3
|
-
import { LOCALHOST_MODE, STANDALONE_MODE,
|
|
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,29 +12,32 @@ __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.
|
|
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_MEMORY, STORAGE_LOCALSTORAGE,
|
|
22
|
+
if (typeof storage !== 'function' || [STORAGE_MEMORY, STORAGE_LOCALSTORAGE, STORAGE_PLUGGABLE].indexOf(storage.type) === -1) {
|
|
21
23
|
storage = InMemoryStorageCSFactory;
|
|
22
|
-
log.
|
|
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
|
}
|
|
28
|
-
// @TODO check behaviour
|
|
29
30
|
if ([LOCALHOST_MODE, STANDALONE_MODE].indexOf(mode) === -1) {
|
|
30
31
|
// Consumer modes require an async storage
|
|
31
|
-
if (storage.type !==
|
|
32
|
-
throw new Error('A
|
|
32
|
+
if (storage.type !== STORAGE_PLUGGABLE)
|
|
33
|
+
throw new Error('A PluggableStorage instance is required on consumer mode');
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
35
36
|
// Standalone and localhost modes require a sync storage
|
|
36
|
-
if (storage.type ===
|
|
37
|
-
|
|
37
|
+
if (storage.type === STORAGE_PLUGGABLE) {
|
|
38
|
+
storage = InMemoryStorageCSFactory;
|
|
39
|
+
log.error(ERROR_STORAGE_INVALID, [' It requires consumer mode.']);
|
|
40
|
+
}
|
|
38
41
|
}
|
|
39
42
|
// return default InMemory storage if provided one is not valid
|
|
40
43
|
return storage;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splitsoftware/splitio-commons",
|
|
3
|
-
"version": "1.0.1-rc.
|
|
3
|
+
"version": "1.0.1-rc.4",
|
|
4
4
|
"description": "Split Javascript SDK common components",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "esm/index.js",
|
|
@@ -49,10 +49,9 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/google.analytics": "0.0.40",
|
|
52
|
-
"@types/ioredis": "^4.
|
|
52
|
+
"@types/ioredis": "^4.28.0",
|
|
53
53
|
"@types/jest": "^27.0.0",
|
|
54
54
|
"@types/lodash": "^4.14.162",
|
|
55
|
-
"@types/node": "^14.14.7",
|
|
56
55
|
"@types/object-assign": "^4.0.30",
|
|
57
56
|
"@typescript-eslint/eslint-plugin": "^4.2.0",
|
|
58
57
|
"@typescript-eslint/parser": "^4.2.0",
|
|
@@ -61,7 +60,7 @@
|
|
|
61
60
|
"eslint": "^7.32.0",
|
|
62
61
|
"eslint-plugin-compat": "3.7.0",
|
|
63
62
|
"fetch-mock": "^9.10.7",
|
|
64
|
-
"ioredis": "^4.
|
|
63
|
+
"ioredis": "^4.28.0",
|
|
65
64
|
"jest": "^27.2.3",
|
|
66
65
|
"jest-localstorage-mock": "^2.4.3",
|
|
67
66
|
"js-yaml": "^3.14.0",
|
package/src/logger/constants.ts
CHANGED
|
@@ -91,9 +91,8 @@ export const WARN_INTEGRATION_INVALID = 218;
|
|
|
91
91
|
export const WARN_SPLITS_FILTER_IGNORED = 219;
|
|
92
92
|
export const WARN_SPLITS_FILTER_INVALID = 220;
|
|
93
93
|
export const WARN_SPLITS_FILTER_EMPTY = 221;
|
|
94
|
-
export const
|
|
95
|
-
export const
|
|
96
|
-
export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 224;
|
|
94
|
+
export const WARN_API_KEY = 222;
|
|
95
|
+
export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
|
|
97
96
|
|
|
98
97
|
export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
|
|
99
98
|
export const ERROR_LOGLEVEL_INVALID = 301;
|
|
@@ -119,6 +118,7 @@ export const ERROR_EMPTY_ARRAY = 320;
|
|
|
119
118
|
export const ERROR_INVALID_IMPRESSIONS_MODE = 321;
|
|
120
119
|
export const ERROR_HTTP = 322;
|
|
121
120
|
export const ERROR_LOCALHOST_MODULE_REQUIRED = 323;
|
|
121
|
+
export const ERROR_STORAGE_INVALID = 324;
|
|
122
122
|
|
|
123
123
|
// Log prefixes (a.k.a. tags or categories)
|
|
124
124
|
export const LOG_PREFIX_SETTINGS = 'settings';
|
|
@@ -30,5 +30,6 @@ export const codesError: [number, string][] = [
|
|
|
30
30
|
[c.ERROR_EMPTY_ARRAY, '%s: %s must be a non-empty array.'],
|
|
31
31
|
// initialization / settings validation
|
|
32
32
|
[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.'],
|
|
33
|
-
[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_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.'],
|
|
34
|
+
[c.ERROR_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid.%s Fallbacking into default MEMORY storage'],
|
|
34
35
|
];
|
|
@@ -29,7 +29,6 @@ export const codesWarn: [number, string][] = codesError.concat([
|
|
|
29
29
|
[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.'],
|
|
30
30
|
[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".'],
|
|
31
31
|
[c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': splitFilters configuration must be a non-empty array of filter objects.'],
|
|
32
|
-
[c.WARN_STORAGE_INVALID, c.LOG_PREFIX_SETTINGS+': The provided storage is invalid. Fallbacking into default MEMORY storage'],
|
|
33
32
|
[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'],
|
|
34
33
|
|
|
35
34
|
[c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
40
40
|
matchingKey: getMatching(settings.core.key),
|
|
41
41
|
splitFiltersValidation: settings.sync.__splitFiltersValidation,
|
|
42
42
|
|
|
43
|
-
// ATM, only used by
|
|
43
|
+
// ATM, only used by PluggableStorage
|
|
44
44
|
mode: settings.mode,
|
|
45
45
|
|
|
46
46
|
// Callback used to emit SDK_READY in consumer mode, where `syncManagerFactory` is undefined
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ISignalListener } from '../listeners/types';
|
|
|
3
3
|
import { ILogger } from '../logger/types';
|
|
4
4
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
5
5
|
import { ISdkClientFactoryParams } from '../sdkClient/types';
|
|
6
|
-
import { IFetch, ISplitApi } from '../services/types';
|
|
6
|
+
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
|
|
7
7
|
import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
|
|
8
8
|
import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
|
|
9
9
|
import { IImpressionObserver } from '../trackers/impressionObserver/types';
|
|
@@ -16,7 +16,7 @@ import { SplitIO, ISettings, IEventEmitter } from '../types';
|
|
|
16
16
|
export interface IPlatform {
|
|
17
17
|
getOptions?: () => object
|
|
18
18
|
getFetch?: () => (IFetch | undefined)
|
|
19
|
-
getEventSource?: () => (
|
|
19
|
+
getEventSource?: () => (IEventSourceConstructor | undefined)
|
|
20
20
|
EventEmitter: new () => IEventEmitter
|
|
21
21
|
}
|
|
22
22
|
|
package/src/services/splitApi.ts
CHANGED
|
@@ -16,7 +16,10 @@ function userKeyToQueryParam(userKey: string) {
|
|
|
16
16
|
* @param settings validated settings object
|
|
17
17
|
* @param platform object containing environment-specific `getFetch` and `getOptions` dependencies
|
|
18
18
|
*/
|
|
19
|
-
export function splitApiFactory(
|
|
19
|
+
export function splitApiFactory(
|
|
20
|
+
settings: Pick<ISettings, 'urls' | 'sync' | 'log' | 'version' | 'runtime' | 'core'>,
|
|
21
|
+
platform: Pick<IPlatform, 'getFetch' | 'getOptions'>
|
|
22
|
+
): ISplitApi {
|
|
20
23
|
|
|
21
24
|
const urls = settings.urls;
|
|
22
25
|
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
package/src/services/types.ts
CHANGED
|
@@ -50,8 +50,8 @@ export type IPostMetricsCounters = (body: string) => Promise<IResponse>
|
|
|
50
50
|
export type IPostMetricsTimes = (body: string) => Promise<IResponse>
|
|
51
51
|
|
|
52
52
|
export interface ISplitApi {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
getSdkAPIHealthCheck: IHealthCheckAPI
|
|
54
|
+
getEventsAPIHealthCheck: IHealthCheckAPI
|
|
55
55
|
fetchAuth: IFetchAuth
|
|
56
56
|
fetchSplitChanges: IFetchSplitChanges
|
|
57
57
|
fetchSegmentChanges: IFetchSegmentChanges
|
|
@@ -62,3 +62,17 @@ export interface ISplitApi {
|
|
|
62
62
|
postMetricsCounters: IPostMetricsCounters
|
|
63
63
|
postMetricsTimes: IPostMetricsTimes
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
// Minimal version of EventSource API used by the SDK
|
|
67
|
+
interface EventSourceEventMap {
|
|
68
|
+
'error': Event
|
|
69
|
+
'message': MessageEvent
|
|
70
|
+
'open': Event
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface IEventSource {
|
|
74
|
+
addEventListener<K extends keyof EventSourceEventMap>(type: K, listener: (this: IEventSource, ev: EventSourceEventMap[K]) => any): void
|
|
75
|
+
close(): void
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type IEventSourceConstructor = new (url: string, eventSourceInitDict?: any) => IEventSource
|
|
@@ -30,7 +30,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
|
|
|
30
30
|
|
|
31
31
|
// subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
32
32
|
redisClient.on('connect', () => {
|
|
33
|
-
|
|
33
|
+
onReadyCb();
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IPluggableStorageWrapper, IEventsCacheAsync } from '../types';
|
|
2
2
|
import { IMetadata } from '../../dtos/types';
|
|
3
3
|
import { SplitIO } from '../../types';
|
|
4
4
|
import { ILogger } from '../../logger/types';
|
|
@@ -8,11 +8,11 @@ import { StoredEventWithMetadata } from '../../sync/submitters/types';
|
|
|
8
8
|
export class EventsCachePluggable implements IEventsCacheAsync {
|
|
9
9
|
|
|
10
10
|
private readonly log: ILogger;
|
|
11
|
-
private readonly wrapper:
|
|
11
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
12
12
|
private readonly key: string;
|
|
13
13
|
private readonly metadata: IMetadata;
|
|
14
14
|
|
|
15
|
-
constructor(log: ILogger, key: string, wrapper:
|
|
15
|
+
constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) {
|
|
16
16
|
this.log = log;
|
|
17
17
|
this.key = key;
|
|
18
18
|
this.wrapper = wrapper;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IPluggableStorageWrapper, IImpressionsCacheAsync } from '../types';
|
|
2
2
|
import { IMetadata } from '../../dtos/types';
|
|
3
3
|
import { ImpressionDTO } from '../../types';
|
|
4
4
|
import { ILogger } from '../../logger/types';
|
|
@@ -8,10 +8,10 @@ export class ImpressionsCachePluggable implements IImpressionsCacheAsync {
|
|
|
8
8
|
|
|
9
9
|
private readonly log: ILogger;
|
|
10
10
|
private readonly key: string;
|
|
11
|
-
private readonly wrapper:
|
|
11
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
12
12
|
private readonly metadata: IMetadata;
|
|
13
13
|
|
|
14
|
-
constructor(log: ILogger, key: string, wrapper:
|
|
14
|
+
constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, metadata: IMetadata) {
|
|
15
15
|
this.log = log;
|
|
16
16
|
this.key = key;
|
|
17
17
|
this.wrapper = wrapper;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable no-unused-vars */
|
|
3
3
|
import { isNaNNumber } from '../../utils/lang';
|
|
4
4
|
import KeyBuilderSS from '../KeyBuilderSS';
|
|
5
|
-
import {
|
|
5
|
+
import { IPluggableStorageWrapper, ISegmentsCacheAsync } from '../types';
|
|
6
6
|
import { ILogger } from '../../logger/types';
|
|
7
7
|
import { LOG_PREFIX } from './constants';
|
|
8
8
|
import { _Set } from '../../utils/lang/sets';
|
|
@@ -14,9 +14,9 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
14
14
|
|
|
15
15
|
private readonly log: ILogger;
|
|
16
16
|
private readonly keys: KeyBuilderSS;
|
|
17
|
-
private readonly wrapper:
|
|
17
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
18
18
|
|
|
19
|
-
constructor(log: ILogger, keys: KeyBuilderSS, wrapper:
|
|
19
|
+
constructor(log: ILogger, keys: KeyBuilderSS, wrapper: IPluggableStorageWrapper) {
|
|
20
20
|
this.log = log;
|
|
21
21
|
this.keys = keys;
|
|
22
22
|
this.wrapper = wrapper;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
2
2
|
import KeyBuilder from '../KeyBuilder';
|
|
3
|
-
import {
|
|
3
|
+
import { IPluggableStorageWrapper } from '../types';
|
|
4
4
|
import { ILogger } from '../../logger/types';
|
|
5
5
|
import { ISplit } from '../../dtos/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
@@ -13,15 +13,15 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
|
13
13
|
|
|
14
14
|
private readonly log: ILogger;
|
|
15
15
|
private readonly keys: KeyBuilder;
|
|
16
|
-
private readonly wrapper:
|
|
16
|
+
private readonly wrapper: IPluggableStorageWrapper;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Create a SplitsCache that uses a
|
|
19
|
+
* Create a SplitsCache that uses a storage wrapper.
|
|
20
20
|
* @param log Logger instance.
|
|
21
21
|
* @param keys Key builder.
|
|
22
22
|
* @param wrapper Adapted wrapper storage.
|
|
23
23
|
*/
|
|
24
|
-
constructor(log: ILogger, keys: KeyBuilder, wrapper:
|
|
24
|
+
constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) {
|
|
25
25
|
super();
|
|
26
26
|
this.log = log;
|
|
27
27
|
this.keys = keys;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LOG_PREFIX = 'storage:pluggable:';
|
|
1
|
+
export const LOG_PREFIX = 'storage:pluggable: ';
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IPluggableStorageWrapper } from '../types';
|
|
2
2
|
import { startsWith, toNumber } from '../../utils/lang';
|
|
3
3
|
import { ISet, setToArray, _Set } from '../../utils/lang/sets';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Creates a
|
|
6
|
+
* Creates a IPluggableStorageWrapper implementation that stores items in memory.
|
|
7
7
|
* The `_cache` property is the object were items are stored.
|
|
8
8
|
* Intended for testing purposes.
|
|
9
9
|
*
|
|
10
10
|
* @param connDelay delay in millis for `connect` resolve. If not provided, `connect` resolves inmediatelly.
|
|
11
11
|
*/
|
|
12
|
-
export function inMemoryWrapperFactory(connDelay?: number):
|
|
12
|
+
export function inMemoryWrapperFactory(connDelay?: number): IPluggableStorageWrapper & { _cache: Record<string, string | string[] | ISet<string>>, _setConnDelay(connDelay: number): void } {
|
|
13
13
|
|
|
14
14
|
let _cache: Record<string, string | string[] | ISet<string>> = {};
|
|
15
15
|
let _connDelay = connDelay;
|
|
@@ -112,7 +112,7 @@ export function inMemoryWrapperFactory(connDelay?: number): ICustomStorageWrappe
|
|
|
112
112
|
return Promise.reject('key is not a set');
|
|
113
113
|
},
|
|
114
114
|
|
|
115
|
-
// always connects and
|
|
115
|
+
// always connects and disconnects
|
|
116
116
|
connect() {
|
|
117
117
|
if (typeof _connDelay === 'number') {
|
|
118
118
|
return new Promise(res => setTimeout(res, _connDelay));
|
|
@@ -120,7 +120,7 @@ export function inMemoryWrapperFactory(connDelay?: number): ICustomStorageWrappe
|
|
|
120
120
|
return Promise.resolve();
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
|
-
|
|
123
|
+
disconnect() { return Promise.resolve(); },
|
|
124
124
|
|
|
125
125
|
// for testing
|
|
126
126
|
_setConnDelay(connDelay: number) {
|
|
@@ -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,16 +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 { CONSUMER_PARTIAL_MODE,
|
|
11
|
+
import { CONSUMER_PARTIAL_MODE, STORAGE_PLUGGABLE } from '../../utils/constants';
|
|
12
12
|
import ImpressionsCacheInMemory from '../inMemory/ImpressionsCacheInMemory';
|
|
13
13
|
import EventsCacheInMemory from '../inMemory/EventsCacheInMemory';
|
|
14
|
+
import ImpressionCountsCacheInMemory from '../inMemory/ImpressionCountsCacheInMemory';
|
|
14
15
|
|
|
15
|
-
const NO_VALID_WRAPPER = 'Expecting
|
|
16
|
+
const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
|
|
16
17
|
const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
|
|
17
18
|
|
|
18
19
|
export interface PluggableStorageOptions {
|
|
19
20
|
prefix?: string
|
|
20
|
-
wrapper:
|
|
21
|
+
wrapper: IPluggableStorageWrapper
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -35,7 +36,7 @@ function validatePluggableStorageOptions(options: any) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// subscription to wrapper connect event in order to emit SDK_READY event
|
|
38
|
-
function wrapperConnect(wrapper:
|
|
39
|
+
function wrapperConnect(wrapper: IPluggableStorageWrapper, onReadyCb: (error?: any) => void) {
|
|
39
40
|
wrapper.connect().then(() => {
|
|
40
41
|
onReadyCb();
|
|
41
42
|
}).catch((e) => {
|
|
@@ -62,24 +63,25 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
62
63
|
|
|
63
64
|
const prefix = validatePrefix(options.prefix);
|
|
64
65
|
|
|
65
|
-
function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize }: IStorageFactoryParams): IStorageAsync {
|
|
66
|
+
function PluggableStorageFactory({ log, metadata, onReadyCb, mode, eventsQueueSize, optimize }: IStorageFactoryParams): IStorageAsync {
|
|
66
67
|
const keys = new KeyBuilderSS(prefix, metadata);
|
|
67
68
|
const wrapper = wrapperAdapter(log, options.wrapper);
|
|
68
69
|
const isPartialConsumer = mode === CONSUMER_PARTIAL_MODE;
|
|
69
70
|
|
|
70
|
-
//
|
|
71
|
+
// Connects to wrapper and emits SDK_READY event on main client
|
|
71
72
|
wrapperConnect(wrapper, onReadyCb);
|
|
72
73
|
|
|
73
74
|
return {
|
|
74
75
|
splits: new SplitsCachePluggable(log, keys, wrapper),
|
|
75
76
|
segments: new SegmentsCachePluggable(log, keys, wrapper),
|
|
76
77
|
impressions: isPartialConsumer ? new ImpressionsCacheInMemory() : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
|
|
78
|
+
impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
|
|
77
79
|
events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
|
|
78
80
|
// @TODO add telemetry cache when required
|
|
79
81
|
|
|
80
|
-
// Disconnect the underlying storage
|
|
82
|
+
// Disconnect the underlying storage
|
|
81
83
|
destroy() {
|
|
82
|
-
return wrapper.
|
|
84
|
+
return wrapper.disconnect();
|
|
83
85
|
},
|
|
84
86
|
|
|
85
87
|
// emits SDK_READY event on shared clients and returns a reference to the storage
|
|
@@ -87,13 +89,13 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
87
89
|
wrapperConnect(wrapper, onReadyCb);
|
|
88
90
|
return {
|
|
89
91
|
...this,
|
|
90
|
-
// no-op destroy, to
|
|
92
|
+
// no-op destroy, to disconnect the wrapper only when the main client is destroyed
|
|
91
93
|
destroy() { }
|
|
92
94
|
};
|
|
93
95
|
}
|
|
94
96
|
};
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
PluggableStorageFactory.type =
|
|
99
|
+
PluggableStorageFactory.type = STORAGE_PLUGGABLE;
|
|
98
100
|
return PluggableStorageFactory;
|
|
99
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
|
@@ -4,9 +4,9 @@ import { StoredEventWithMetadata, StoredImpressionWithMetadata } from '../sync/s
|
|
|
4
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
|
|
|
@@ -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
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 */
|
|
@@ -430,7 +430,7 @@ export interface IStorageFactoryParams {
|
|
|
430
430
|
matchingKey?: string, /* undefined on server-side SDKs */
|
|
431
431
|
splitFiltersValidation?: ISplitFiltersValidation,
|
|
432
432
|
|
|
433
|
-
// ATM, only used by
|
|
433
|
+
// ATM, only used by PluggableStorage
|
|
434
434
|
mode?: SDKMode,
|
|
435
435
|
|
|
436
436
|
// This callback is invoked when the storage is ready to be used. Error-first callback style: if an error is passed,
|
|
@@ -440,7 +440,7 @@ export interface IStorageFactoryParams {
|
|
|
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);
|
|
@@ -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
|
@@ -7,15 +7,16 @@ import { IStorageFactoryParams, IStorageSync, IStorageAsync, IStorageSyncFactory
|
|
|
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
|
|
|
@@ -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,
|