@splitsoftware/splitio-commons 2.0.0-rc.0 → 2.0.0-rc.1
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/CHANGES.txt +2 -1
- package/cjs/evaluator/Engine.js +1 -1
- package/cjs/evaluator/index.js +1 -1
- package/cjs/readiness/readinessManager.js +13 -2
- package/cjs/sdkClient/sdkClientMethodCS.js +0 -1
- package/cjs/sdkFactory/index.js +26 -8
- package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
- package/cjs/storages/inLocalStorage/index.js +1 -1
- package/cjs/storages/inMemory/InMemoryStorageCS.js +2 -2
- package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
- package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
- package/cjs/storages/inRedis/RedisAdapter.js +2 -1
- package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
- package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
- package/cjs/storages/pluggable/inMemoryWrapper.js +2 -1
- package/cjs/sync/offline/syncManagerOffline.js +18 -11
- package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -1
- package/cjs/sync/syncManagerOnline.js +20 -21
- package/cjs/trackers/eventTracker.js +12 -10
- package/cjs/trackers/impressionsTracker.js +16 -14
- package/cjs/trackers/uniqueKeysTracker.js +5 -3
- package/cjs/utils/lang/sets.js +12 -2
- package/esm/evaluator/Engine.js +1 -1
- package/esm/evaluator/index.js +2 -2
- package/esm/readiness/readinessManager.js +13 -2
- package/esm/sdkClient/sdkClientMethodCS.js +0 -1
- package/esm/sdkFactory/index.js +26 -8
- package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +3 -2
- package/esm/storages/inLocalStorage/index.js +1 -1
- package/esm/storages/inMemory/InMemoryStorageCS.js +2 -2
- package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
- package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +2 -1
- package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +2 -1
- package/esm/storages/inRedis/RedisAdapter.js +2 -1
- package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
- package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +2 -1
- package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
- package/esm/storages/pluggable/UniqueKeysCachePluggable.js +2 -1
- package/esm/storages/pluggable/inMemoryWrapper.js +2 -1
- package/esm/sync/offline/syncManagerOffline.js +18 -11
- package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -1
- package/esm/sync/syncManagerOnline.js +20 -21
- package/esm/trackers/eventTracker.js +12 -10
- package/esm/trackers/impressionsTracker.js +16 -14
- package/esm/trackers/uniqueKeysTracker.js +5 -3
- package/esm/utils/lang/sets.js +10 -1
- package/package.json +1 -1
- package/src/evaluator/Engine.ts +1 -1
- package/src/evaluator/index.ts +2 -2
- package/src/readiness/readinessManager.ts +12 -3
- package/src/readiness/types.ts +3 -0
- package/src/sdkClient/sdkClientMethodCS.ts +0 -2
- package/src/sdkFactory/index.ts +28 -9
- package/src/sdkFactory/types.ts +2 -0
- package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +3 -2
- package/src/storages/inLocalStorage/index.ts +1 -1
- package/src/storages/inMemory/InMemoryStorageCS.ts +2 -2
- package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
- package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +2 -1
- package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +2 -1
- package/src/storages/inRedis/RedisAdapter.ts +2 -1
- package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
- package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +2 -1
- package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
- package/src/storages/pluggable/UniqueKeysCachePluggable.ts +2 -1
- package/src/storages/pluggable/inMemoryWrapper.ts +2 -1
- package/src/storages/types.ts +3 -9
- package/src/sync/offline/syncManagerOffline.ts +21 -13
- package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
- package/src/sync/polling/updaters/splitChangesUpdater.ts +2 -1
- package/src/sync/syncManagerOnline.ts +17 -17
- package/src/sync/types.ts +1 -1
- package/src/trackers/eventTracker.ts +11 -8
- package/src/trackers/impressionsTracker.ts +13 -10
- package/src/trackers/types.ts +1 -0
- package/src/trackers/uniqueKeysTracker.ts +6 -4
- package/src/utils/lang/sets.ts +11 -1
- package/types/readiness/types.d.ts +3 -0
- package/types/sdkFactory/types.d.ts +1 -0
- package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
- package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
- package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
- package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
- package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
- package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
- package/types/storages/types.d.ts +3 -9
- package/types/sync/types.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +1 -1
- package/types/trackers/impressionsTracker.d.ts +1 -1
- package/types/trackers/types.d.ts +1 -0
- package/types/utils/lang/sets.d.ts +1 -0
package/src/evaluator/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { IStorageAsync, IStorageSync } from '../storages/types';
|
|
|
7
7
|
import { IEvaluationResult } from './types';
|
|
8
8
|
import { SplitIO } from '../types';
|
|
9
9
|
import { ILogger } from '../logger/types';
|
|
10
|
-
import { returnSetsUnion } from '../utils/lang/sets';
|
|
10
|
+
import { returnSetsUnion, setToArray } from '../utils/lang/sets';
|
|
11
11
|
import { WARN_FLAGSET_WITHOUT_FLAGS } from '../logger/constants';
|
|
12
12
|
|
|
13
13
|
const treatmentException = {
|
|
@@ -113,7 +113,7 @@ export function evaluateFeaturesByFlagSets(
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return featureFlags.size ?
|
|
116
|
-
evaluateFeatures(log, key,
|
|
116
|
+
evaluateFeatures(log, key, setToArray(featureFlags), attributes, storage) :
|
|
117
117
|
{};
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -7,6 +7,8 @@ function splitsEventEmitterFactory(EventEmitter: new () => IEventEmitter): ISpli
|
|
|
7
7
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
8
8
|
splitsArrived: false,
|
|
9
9
|
splitsCacheLoaded: false,
|
|
10
|
+
initialized: false,
|
|
11
|
+
initCallbacks: []
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
// `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
|
|
@@ -56,8 +58,8 @@ export function readinessManagerFactory(
|
|
|
56
58
|
// emit SDK_READY_TIMED_OUT
|
|
57
59
|
let hasTimedout = false;
|
|
58
60
|
|
|
59
|
-
function timeout() {
|
|
60
|
-
if (hasTimedout) return;
|
|
61
|
+
function timeout() { // eslint-disable-next-line no-use-before-define
|
|
62
|
+
if (hasTimedout || isReady) return;
|
|
61
63
|
hasTimedout = true;
|
|
62
64
|
syncLastUpdate();
|
|
63
65
|
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
|
|
@@ -65,7 +67,8 @@ export function readinessManagerFactory(
|
|
|
65
67
|
|
|
66
68
|
let readyTimeoutId: ReturnType<typeof setTimeout>;
|
|
67
69
|
if (readyTimeout > 0) {
|
|
68
|
-
readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
70
|
+
if (splits.initialized) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
71
|
+
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
// emit SDK_READY and SDK_UPDATE
|
|
@@ -132,6 +135,12 @@ export function readinessManagerFactory(
|
|
|
132
135
|
// tracking and evaluations, while keeping event listeners to emit SDK_READY_TIMED_OUT event
|
|
133
136
|
setDestroyed() { isDestroyed = true; },
|
|
134
137
|
|
|
138
|
+
init() {
|
|
139
|
+
if (splits.initialized) return;
|
|
140
|
+
splits.initialized = true;
|
|
141
|
+
splits.initCallbacks.forEach(cb => cb());
|
|
142
|
+
},
|
|
143
|
+
|
|
135
144
|
destroy() {
|
|
136
145
|
isDestroyed = true;
|
|
137
146
|
syncLastUpdate();
|
package/src/readiness/types.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ISplitsEventEmitter extends IEventEmitter {
|
|
|
12
12
|
once(event: ISplitsEvent, listener: (...args: any[]) => void): this;
|
|
13
13
|
splitsArrived: boolean
|
|
14
14
|
splitsCacheLoaded: boolean
|
|
15
|
+
initialized: boolean,
|
|
16
|
+
initCallbacks: (() => void)[]
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/** Segments data emitter */
|
|
@@ -59,6 +61,7 @@ export interface IReadinessManager {
|
|
|
59
61
|
timeout(): void,
|
|
60
62
|
setDestroyed(): void,
|
|
61
63
|
destroy(): void,
|
|
64
|
+
init(): void,
|
|
62
65
|
|
|
63
66
|
/** for client-side */
|
|
64
67
|
shared(): IReadinessManager,
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -23,14 +23,20 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
23
23
|
const { settings, platform, storageFactory, splitApiFactory, extraProps,
|
|
24
24
|
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
25
25
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
|
|
26
|
-
filterAdapterFactory } = params;
|
|
26
|
+
filterAdapterFactory, lazyInit } = params;
|
|
27
27
|
const { log, sync: { impressionsMode } } = settings;
|
|
28
28
|
|
|
29
29
|
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
|
|
30
30
|
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// initialization
|
|
33
|
+
let hasInit = false;
|
|
34
|
+
const initCallbacks: (() => void)[] = [];
|
|
35
|
+
|
|
36
|
+
function whenInit(cb: () => void) {
|
|
37
|
+
if (hasInit) cb();
|
|
38
|
+
else initCallbacks.push(cb);
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
|
|
36
42
|
const readiness = sdkReadinessManager.readinessManager;
|
|
@@ -67,8 +73,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
67
73
|
strategy = strategyDebugFactory(observer);
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, integrationsManager, storage.telemetry);
|
|
71
|
-
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
|
|
76
|
+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, whenInit, integrationsManager, storage.telemetry);
|
|
77
|
+
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
|
|
72
78
|
|
|
73
79
|
// splitApi is used by SyncManager and Browser signal listener
|
|
74
80
|
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
|
|
@@ -85,8 +91,21 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
85
91
|
const clientMethod = sdkClientMethodFactory(ctx);
|
|
86
92
|
const managerInstance = sdkManagerFactory(settings, storage.splits, sdkReadinessManager);
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
|
|
95
|
+
function init() {
|
|
96
|
+
if (hasInit) return;
|
|
97
|
+
hasInit = true;
|
|
98
|
+
|
|
99
|
+
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
|
|
100
|
+
validateAndTrackApiKey(log, settings.core.authorizationKey);
|
|
101
|
+
readiness.init();
|
|
102
|
+
uniqueKeysTracker && uniqueKeysTracker.start();
|
|
103
|
+
syncManager && syncManager.start();
|
|
104
|
+
signalListener && signalListener.start();
|
|
105
|
+
|
|
106
|
+
initCallbacks.forEach((cb) => cb());
|
|
107
|
+
initCallbacks.length = 0;
|
|
108
|
+
}
|
|
90
109
|
|
|
91
110
|
log.info(NEW_FACTORY);
|
|
92
111
|
|
|
@@ -107,7 +126,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
107
126
|
settings,
|
|
108
127
|
|
|
109
128
|
destroy() {
|
|
110
|
-
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
|
|
129
|
+
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
111
130
|
}
|
|
112
|
-
}, extraProps && extraProps(ctx));
|
|
131
|
+
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
113
132
|
}
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
|
|
|
68
68
|
* Object parameter with the modules required to create an SDK factory instance
|
|
69
69
|
*/
|
|
70
70
|
export interface ISdkFactoryParams {
|
|
71
|
+
// If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
|
|
72
|
+
lazyInit?: boolean,
|
|
71
73
|
|
|
72
74
|
// The settings must be already validated
|
|
73
75
|
settings: ISettings,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
/* eslint-disable no-unused-vars */
|
|
3
1
|
import { IMySegmentsResponse } from '../dtos/types';
|
|
4
2
|
import { MySegmentsData } from '../sync/polling/types';
|
|
5
3
|
import { ISegmentsCacheSync } from './types';
|
|
@@ -8,18 +6,11 @@ import { ISegmentsCacheSync } from './types';
|
|
|
8
6
|
* This class provides a skeletal implementation of the ISegmentsCacheSync interface
|
|
9
7
|
* to minimize the effort required to implement this interface.
|
|
10
8
|
*/
|
|
11
|
-
export abstract class
|
|
12
|
-
/**
|
|
13
|
-
* For server-side synchronizer: add `segmentKeys` list of keys to `name` segment.
|
|
14
|
-
* For client-side synchronizer: add `name` segment to the cache. `segmentKeys` is undefined.
|
|
15
|
-
*/
|
|
16
|
-
abstract addToSegment(name: string, segmentKeys?: string[]): boolean
|
|
9
|
+
export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync {
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*/
|
|
22
|
-
abstract removeFromSegment(name: string, segmentKeys?: string[]): boolean
|
|
11
|
+
protected abstract addSegment(name: string): boolean
|
|
12
|
+
protected abstract removeSegment(name: string): boolean
|
|
13
|
+
protected abstract setChangeNumber(changeNumber?: number): boolean | void
|
|
23
14
|
|
|
24
15
|
/**
|
|
25
16
|
* For server-side synchronizer: check if `key` is in `name` segment.
|
|
@@ -34,11 +25,10 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
34
25
|
this.resetSegments({});
|
|
35
26
|
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
registerSegments(names: string[]): boolean { return false; }
|
|
28
|
+
|
|
29
|
+
// No-op. Not used in client-side.
|
|
30
|
+
registerSegments(): boolean { return false; }
|
|
31
|
+
update() { return false; }
|
|
42
32
|
|
|
43
33
|
/**
|
|
44
34
|
* For server-side synchronizer: get the list of segments to fetch changes.
|
|
@@ -52,11 +42,6 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
52
42
|
*/
|
|
53
43
|
abstract getKeysCount(): number
|
|
54
44
|
|
|
55
|
-
/**
|
|
56
|
-
* For server-side synchronizer: change number of `name` segment.
|
|
57
|
-
* For client-side synchronizer: change number of mySegments.
|
|
58
|
-
*/
|
|
59
|
-
abstract setChangeNumber(name?: string, changeNumber?: number): boolean | void
|
|
60
45
|
abstract getChangeNumber(name: string): number
|
|
61
46
|
|
|
62
47
|
/**
|
|
@@ -64,7 +49,7 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
64
49
|
* For client-side synchronizer: it resets or updates the cache.
|
|
65
50
|
*/
|
|
66
51
|
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
67
|
-
this.setChangeNumber(
|
|
52
|
+
this.setChangeNumber(segmentsData.cn);
|
|
68
53
|
|
|
69
54
|
const { added, removed } = segmentsData as MySegmentsData;
|
|
70
55
|
|
|
@@ -72,11 +57,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
72
57
|
let isDiff = false;
|
|
73
58
|
|
|
74
59
|
added.forEach(segment => {
|
|
75
|
-
isDiff = this.
|
|
60
|
+
isDiff = this.addSegment(segment) || isDiff;
|
|
76
61
|
});
|
|
77
62
|
|
|
78
63
|
removed.forEach(segment => {
|
|
79
|
-
isDiff = this.
|
|
64
|
+
isDiff = this.removeSegment(segment) || isDiff;
|
|
80
65
|
});
|
|
81
66
|
|
|
82
67
|
return isDiff;
|
|
@@ -97,11 +82,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
97
82
|
|
|
98
83
|
// Slowest path => add and/or remove segments
|
|
99
84
|
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
100
|
-
this.
|
|
85
|
+
this.removeSegment(storedSegmentKeys[removeIndex]);
|
|
101
86
|
}
|
|
102
87
|
|
|
103
88
|
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
104
|
-
this.
|
|
89
|
+
this.addSegment(names[addIndex]);
|
|
105
90
|
}
|
|
106
91
|
|
|
107
92
|
return true;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ILogger } from '../../logger/types';
|
|
2
2
|
import { isNaNNumber } from '../../utils/lang';
|
|
3
|
-
import {
|
|
3
|
+
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
4
4
|
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
5
5
|
import { LOG_PREFIX, DEFINED } from './constants';
|
|
6
6
|
|
|
7
|
-
export class MySegmentsCacheInLocal extends
|
|
7
|
+
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
8
8
|
|
|
9
9
|
private readonly keys: MySegmentsKeyBuilder;
|
|
10
10
|
private readonly log: ILogger;
|
|
@@ -16,7 +16,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
16
16
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
protected addSegment(name: string): boolean {
|
|
20
20
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
21
21
|
|
|
22
22
|
try {
|
|
@@ -29,7 +29,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
protected removeSegment(name: string): boolean {
|
|
33
33
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
34
34
|
|
|
35
35
|
try {
|
|
@@ -61,7 +61,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
61
61
|
return 1;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
setChangeNumber(
|
|
64
|
+
protected setChangeNumber(changeNumber?: number) {
|
|
65
65
|
try {
|
|
66
66
|
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
67
67
|
else localStorage.removeItem(this.keys.buildTillKey());
|
|
@@ -6,6 +6,7 @@ import { ILogger } from '../../logger/types';
|
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
8
8
|
import { getStorageHash } from '../KeyBuilder';
|
|
9
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
@@ -281,7 +282,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
281
282
|
const flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
282
283
|
flagSetCache.add(featureFlag.name);
|
|
283
284
|
|
|
284
|
-
localStorage.setItem(flagSetKey, JSON.stringify(
|
|
285
|
+
localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
285
286
|
});
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -308,7 +309,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
308
309
|
return;
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
localStorage.setItem(flagSetKey, JSON.stringify(
|
|
312
|
+
localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
}
|
|
@@ -65,7 +65,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
|
|
|
65
65
|
this.uniqueKeys?.clear();
|
|
66
66
|
},
|
|
67
67
|
|
|
68
|
-
// When using shared
|
|
68
|
+
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
69
69
|
shared(matchingKey: string) {
|
|
70
70
|
|
|
71
71
|
return {
|
|
@@ -41,7 +41,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
41
41
|
this.uniqueKeys && this.uniqueKeys.clear();
|
|
42
42
|
},
|
|
43
43
|
|
|
44
|
-
// When using shared
|
|
44
|
+
// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
|
|
45
45
|
shared() {
|
|
46
46
|
return {
|
|
47
47
|
splits: this.splits,
|
|
@@ -63,7 +63,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
// @TODO revisit storage logic in localhost mode
|
|
66
|
-
// No tracking
|
|
66
|
+
// No tracking in localhost mode to avoid memory leaks: https://github.com/splitio/javascript-commons/issues/181
|
|
67
67
|
if (params.settings.mode === LOCALHOST_MODE) {
|
|
68
68
|
const noopTrack = () => true;
|
|
69
69
|
storage.impressions.track = noopTrack;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Default MySegmentsCacheInMemory implementation that stores MySegments in memory.
|
|
5
5
|
* Supported by all JS runtimes.
|
|
6
6
|
*/
|
|
7
|
-
export class MySegmentsCacheInMemory extends
|
|
7
|
+
export class MySegmentsCacheInMemory extends AbstractMySegmentsCacheSync {
|
|
8
8
|
|
|
9
9
|
private segmentCache: Record<string, boolean> = {};
|
|
10
10
|
private cn?: number;
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
protected addSegment(name: string): boolean {
|
|
13
13
|
if (this.segmentCache[name]) return false;
|
|
14
14
|
|
|
15
15
|
this.segmentCache[name] = true;
|
|
@@ -17,7 +17,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
protected removeSegment(name: string): boolean {
|
|
21
21
|
if (!this.segmentCache[name]) return false;
|
|
22
22
|
|
|
23
23
|
delete this.segmentCache[name];
|
|
@@ -30,7 +30,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
setChangeNumber(
|
|
33
|
+
protected setChangeNumber(changeNumber?: number) {
|
|
34
34
|
this.cn = changeNumber;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -1,35 +1,24 @@
|
|
|
1
|
-
import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
|
|
2
1
|
import { isIntegerNumber } from '../../utils/lang';
|
|
2
|
+
import { ISegmentsCacheSync } from '../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Default ISplitsCacheSync implementation that stores
|
|
6
|
-
* Supported by all JS runtimes.
|
|
5
|
+
* Default ISplitsCacheSync implementation for server-side that stores segments definitions in memory.
|
|
7
6
|
*/
|
|
8
|
-
export class SegmentsCacheInMemory
|
|
7
|
+
export class SegmentsCacheInMemory implements ISegmentsCacheSync {
|
|
9
8
|
|
|
10
9
|
private segmentCache: Record<string, Set<string>> = {};
|
|
11
10
|
private segmentChangeNumber: Record<string, number> = {};
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const keySet = values ? values : new Set<string>();
|
|
12
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
13
|
+
const keySet = this.segmentCache[name] || new Set<string>();
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
addedKeys.forEach(k => keySet.add(k));
|
|
16
|
+
removedKeys.forEach(k => keySet.delete(k));
|
|
18
17
|
|
|
19
18
|
this.segmentCache[name] = keySet;
|
|
19
|
+
this.segmentChangeNumber[name] = changeNumber;
|
|
20
20
|
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
removeFromSegment(name: string, segmentKeys: string[]): boolean {
|
|
25
|
-
const values = this.segmentCache[name];
|
|
26
|
-
const keySet = values ? values : new Set<string>();
|
|
27
|
-
|
|
28
|
-
segmentKeys.forEach(k => keySet.delete(k));
|
|
29
|
-
|
|
30
|
-
this.segmentCache[name] = keySet;
|
|
31
|
-
|
|
32
|
-
return true;
|
|
21
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
33
22
|
}
|
|
34
23
|
|
|
35
24
|
isInSegment(name: string, key: string): boolean {
|
|
@@ -73,16 +62,13 @@ export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
73
62
|
}, 0);
|
|
74
63
|
}
|
|
75
64
|
|
|
76
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
77
|
-
this.segmentChangeNumber[name] = changeNumber;
|
|
78
|
-
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
65
|
getChangeNumber(name: string) {
|
|
83
66
|
const value = this.segmentChangeNumber[name];
|
|
84
67
|
|
|
85
68
|
return isIntegerNumber(value) ? value : -1;
|
|
86
69
|
}
|
|
87
70
|
|
|
71
|
+
// No-op. Not used in server-side
|
|
72
|
+
resetSegments() { return false; }
|
|
73
|
+
|
|
88
74
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IUniqueKeysCacheBase } from '../types';
|
|
2
2
|
import { UniqueKeysPayloadSs } from '../../sync/submitters/types';
|
|
3
3
|
import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
|
|
4
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Converts `uniqueKeys` data from cache into request payload for SS.
|
|
@@ -10,7 +11,7 @@ export function fromUniqueKeysCollector(uniqueKeys: { [featureName: string]: Set
|
|
|
10
11
|
const featureNames = Object.keys(uniqueKeys);
|
|
11
12
|
for (let i = 0; i < featureNames.length; i++) {
|
|
12
13
|
const featureName = featureNames[i];
|
|
13
|
-
const userKeys =
|
|
14
|
+
const userKeys = setToArray(uniqueKeys[featureName]);
|
|
14
15
|
const uniqueKeysPayload = {
|
|
15
16
|
f: featureName,
|
|
16
17
|
ks: userKeys
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IUniqueKeysCacheBase } from '../types';
|
|
2
2
|
import { UniqueKeysPayloadCs } from '../../sync/submitters/types';
|
|
3
3
|
import { DEFAULT_CACHE_SIZE } from '../inRedis/constants';
|
|
4
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
4
5
|
|
|
5
6
|
export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
6
7
|
|
|
@@ -70,7 +71,7 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
70
71
|
const userKeys = Object.keys(uniqueKeys);
|
|
71
72
|
for (let k = 0; k < userKeys.length; k++) {
|
|
72
73
|
const userKey = userKeys[k];
|
|
73
|
-
const featureNames =
|
|
74
|
+
const featureNames = setToArray(uniqueKeys[userKey]);
|
|
74
75
|
const uniqueKeysPayload = {
|
|
75
76
|
k: userKey,
|
|
76
77
|
fs: featureNames
|
|
@@ -3,6 +3,7 @@ import { ILogger } from '../../logger/types';
|
|
|
3
3
|
import { merge, isString } from '../../utils/lang';
|
|
4
4
|
import { thenable } from '../../utils/promise/thenable';
|
|
5
5
|
import { timeout } from '../../utils/promise/timeout';
|
|
6
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
6
7
|
|
|
7
8
|
const LOG_PREFIX = 'storage:redis-adapter: ';
|
|
8
9
|
|
|
@@ -149,7 +150,7 @@ export class RedisAdapter extends ioredis {
|
|
|
149
150
|
if (instance._runningCommands.size > 0) {
|
|
150
151
|
instance.log.info(LOG_PREFIX + `Attempting to disconnect but there are ${instance._runningCommands.size} commands still waiting for resolution. Defering disconnection until those finish.`);
|
|
151
152
|
|
|
152
|
-
Promise.all(
|
|
153
|
+
Promise.all(setToArray(instance._runningCommands))
|
|
153
154
|
.then(() => {
|
|
154
155
|
instance.log.debug(LOG_PREFIX + 'Pending commands finished successfully, disconnecting.');
|
|
155
156
|
originalMethod.apply(instance, params);
|
|
@@ -17,24 +17,21 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
|
|
|
17
17
|
this.keys = keys;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
22
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
23
|
+
* or rejected if it fails (e.g., Redis operation fails).
|
|
24
|
+
*/
|
|
25
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
21
26
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
removeFromSegment(name: string, segmentKeys: string[]) {
|
|
31
|
-
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
32
|
-
|
|
33
|
-
if (segmentKeys.length) {
|
|
34
|
-
return this.redis.srem(segmentKey, segmentKeys).then(() => true);
|
|
35
|
-
} else {
|
|
36
|
-
return Promise.resolve(true);
|
|
37
|
-
}
|
|
28
|
+
return Promise.all([
|
|
29
|
+
addedKeys.length && this.redis.sadd(segmentKey, addedKeys),
|
|
30
|
+
removedKeys.length && this.redis.srem(segmentKey, removedKeys),
|
|
31
|
+
this.redis.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
32
|
+
]).then(() => {
|
|
33
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
34
|
+
});
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
isInSegment(name: string, key: string) {
|
|
@@ -43,12 +40,6 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
|
|
|
43
40
|
).then(matches => matches !== 0);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
47
|
-
return this.redis.set(
|
|
48
|
-
this.keys.buildSegmentTillKey(name), changeNumber + ''
|
|
49
|
-
).then(status => status === 'OK');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
43
|
getChangeNumber(name: string) {
|
|
53
44
|
return this.redis.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => {
|
|
54
45
|
const i = parseInt(value as string, 10);
|
|
@@ -5,6 +5,7 @@ import { LOG_PREFIX } from './constants';
|
|
|
5
5
|
import { ILogger } from '../../logger/types';
|
|
6
6
|
import { UniqueKeysItemSs } from '../../sync/submitters/types';
|
|
7
7
|
import type { RedisAdapter } from './RedisAdapter';
|
|
8
|
+
import { setToArray } from '../../utils/lang/sets';
|
|
8
9
|
|
|
9
10
|
export class UniqueKeysCacheInRedis extends UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
10
11
|
|
|
@@ -28,7 +29,7 @@ export class UniqueKeysCacheInRedis extends UniqueKeysCacheInMemory implements I
|
|
|
28
29
|
if (!featureNames.length) return Promise.resolve(false);
|
|
29
30
|
|
|
30
31
|
const uniqueKeysArray = featureNames.map((featureName) => {
|
|
31
|
-
const featureKeys =
|
|
32
|
+
const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
32
33
|
const uniqueKeysPayload = {
|
|
33
34
|
f: featureName,
|
|
34
35
|
ks: featureKeys
|
|
@@ -22,33 +22,20 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* The returned promise is resolved
|
|
27
|
-
* or rejected if wrapper operation fails.
|
|
28
|
-
*/
|
|
29
|
-
addToSegment(name: string, segmentKeys: string[]) {
|
|
30
|
-
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
31
|
-
|
|
32
|
-
if (segmentKeys.length) {
|
|
33
|
-
return this.wrapper.addItems(segmentKey, segmentKeys);
|
|
34
|
-
} else {
|
|
35
|
-
return Promise.resolve();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Remove a list of `segmentKeys` from the given segment `name`.
|
|
41
|
-
* The returned promise is resolved when the operation success
|
|
42
|
-
* or rejected if wrapper operation fails.
|
|
25
|
+
* Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
|
|
26
|
+
* The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
|
|
27
|
+
* or rejected if it fails (e.g., wrapper operation fails).
|
|
43
28
|
*/
|
|
44
|
-
|
|
29
|
+
update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
|
|
45
30
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
return Promise.all<any>([
|
|
33
|
+
addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
|
|
34
|
+
removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
|
|
35
|
+
this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
|
|
36
|
+
]).then(() => {
|
|
37
|
+
return addedKeys.length > 0 || removedKeys.length > 0;
|
|
38
|
+
});
|
|
52
39
|
}
|
|
53
40
|
|
|
54
41
|
/**
|
|
@@ -59,17 +46,6 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
|
|
|
59
46
|
return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
|
|
60
47
|
}
|
|
61
48
|
|
|
62
|
-
/**
|
|
63
|
-
* Set till number for the given segment `name`.
|
|
64
|
-
* The returned promise is resolved when the operation success,
|
|
65
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
66
|
-
*/
|
|
67
|
-
setChangeNumber(name: string, changeNumber: number) {
|
|
68
|
-
return this.wrapper.set(
|
|
69
|
-
this.keys.buildSegmentTillKey(name), changeNumber + ''
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
49
|
/**
|
|
74
50
|
* Get till number or -1 if it's not defined.
|
|
75
51
|
* The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
|