@splitsoftware/splitio-commons 3.0.0-rc.0 → 3.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +8 -3
- package/cjs/evaluator/index.js +11 -11
- package/cjs/logger/constants.js +4 -4
- package/cjs/logger/messages/warn.js +0 -1
- package/cjs/readiness/constants.js +4 -4
- package/cjs/readiness/readinessManager.js +22 -22
- package/cjs/readiness/sdkReadinessManager.js +0 -16
- package/cjs/sdkClient/sdkLifecycle.js +2 -1
- package/cjs/sdkClient/trackMethod.js +2 -2
- package/cjs/sdkFactory/index.js +5 -5
- package/cjs/sdkManager/index.js +2 -2
- package/cjs/services/authProvider.js +67 -0
- package/cjs/services/secureSplitHttpClient.js +39 -0
- package/cjs/services/splitApi.js +15 -6
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +51 -0
- package/cjs/storages/AbstractDefinitionsCacheSync.js +76 -0
- package/cjs/storages/KeyBuilder.js +6 -6
- package/cjs/storages/KeyBuilderCS.js +5 -5
- package/cjs/storages/KeyBuilderSS.js +2 -2
- package/cjs/storages/getRolloutPlan.js +3 -3
- package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +194 -0
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +9 -9
- package/cjs/storages/inLocalStorage/index.js +6 -6
- package/cjs/storages/inLocalStorage/validateCache.js +3 -3
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +121 -0
- package/cjs/storages/inMemory/InMemoryStorage.js +4 -4
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -4
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +3 -3
- package/cjs/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +77 -77
- package/cjs/storages/inRedis/index.js +2 -2
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +212 -0
- package/cjs/storages/pluggable/index.js +2 -2
- package/cjs/storages/setRolloutPlan.js +5 -5
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +5 -5
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/cjs/sync/polling/pollingManagerCS.js +7 -7
- package/cjs/sync/polling/pollingManagerSS.js +3 -3
- package/cjs/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
- package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +2 -3
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -8
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +1 -1
- package/cjs/sync/streaming/SSEHandler/index.js +1 -1
- package/cjs/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +15 -15
- package/cjs/sync/streaming/constants.js +3 -3
- package/cjs/sync/streaming/pushManager.js +13 -13
- package/cjs/sync/submitters/telemetrySubmitter.js +1 -2
- package/cjs/sync/syncManagerOnline.js +10 -8
- package/cjs/utils/Backoff.js +20 -0
- package/cjs/utils/lang/sets.js +4 -4
- package/cjs/utils/settingsValidation/index.js +2 -0
- package/cjs/utils/settingsValidation/url.js +6 -2
- package/esm/evaluator/index.js +11 -11
- package/esm/logger/constants.js +1 -1
- package/esm/logger/messages/warn.js +0 -1
- package/esm/readiness/constants.js +3 -3
- package/esm/readiness/readinessManager.js +23 -23
- package/esm/readiness/sdkReadinessManager.js +1 -17
- package/esm/sdkClient/sdkLifecycle.js +2 -1
- package/esm/sdkClient/trackMethod.js +2 -2
- package/esm/sdkFactory/index.js +6 -6
- package/esm/sdkManager/index.js +2 -2
- package/esm/services/authProvider.js +63 -0
- package/esm/services/secureSplitHttpClient.js +35 -0
- package/esm/services/splitApi.js +15 -6
- package/esm/storages/AbstractDefinitionsCacheAsync.js +48 -0
- package/esm/storages/AbstractDefinitionsCacheSync.js +71 -0
- package/esm/storages/KeyBuilder.js +6 -6
- package/esm/storages/KeyBuilderCS.js +5 -5
- package/esm/storages/KeyBuilderSS.js +2 -2
- package/esm/storages/getRolloutPlan.js +3 -3
- package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +191 -0
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -6
- package/esm/storages/inLocalStorage/index.js +6 -6
- package/esm/storages/inLocalStorage/validateCache.js +3 -3
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +118 -0
- package/esm/storages/inMemory/InMemoryStorage.js +4 -4
- package/esm/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +3 -3
- package/esm/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +76 -76
- package/esm/storages/inRedis/index.js +2 -2
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +209 -0
- package/esm/storages/pluggable/index.js +2 -2
- package/esm/storages/setRolloutPlan.js +5 -5
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +6 -6
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/esm/sync/polling/pollingManagerCS.js +4 -4
- package/esm/sync/polling/pollingManagerSS.js +3 -3
- package/esm/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
- package/esm/sync/polling/syncTasks/segmentsSyncTask.js +2 -3
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +9 -9
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
- package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +2 -2
- package/esm/sync/streaming/SSEHandler/index.js +2 -2
- package/esm/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +14 -14
- package/esm/sync/streaming/constants.js +2 -2
- package/esm/sync/streaming/pushManager.js +14 -14
- package/esm/sync/submitters/telemetrySubmitter.js +1 -2
- package/esm/sync/syncManagerOnline.js +9 -7
- package/esm/utils/Backoff.js +20 -0
- package/esm/utils/lang/sets.js +4 -4
- package/esm/utils/settingsValidation/index.js +2 -0
- package/esm/utils/settingsValidation/url.js +6 -2
- package/package.json +1 -1
- package/src/dtos/types.ts +2 -2
- package/src/evaluator/index.ts +11 -11
- package/src/logger/constants.ts +1 -1
- package/src/logger/messages/warn.ts +0 -1
- package/src/readiness/constants.ts +3 -3
- package/src/readiness/readinessManager.ts +22 -22
- package/src/readiness/sdkReadinessManager.ts +1 -16
- package/src/readiness/types.ts +10 -16
- package/src/sdkClient/sdkLifecycle.ts +2 -1
- package/src/sdkClient/trackMethod.ts +2 -2
- package/src/sdkFactory/index.ts +5 -5
- package/src/sdkManager/index.ts +6 -6
- package/src/services/authProvider.ts +86 -0
- package/src/services/secureSplitHttpClient.ts +44 -0
- package/src/services/splitApi.ts +18 -7
- package/src/services/types.ts +6 -1
- package/src/storages/AbstractDefinitionsCacheAsync.ts +63 -0
- package/src/storages/AbstractDefinitionsCacheSync.ts +95 -0
- package/src/storages/KeyBuilder.ts +6 -6
- package/src/storages/KeyBuilderCS.ts +6 -6
- package/src/storages/KeyBuilderSS.ts +2 -2
- package/src/storages/getRolloutPlan.ts +3 -3
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +234 -0
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +6 -6
- package/src/storages/inLocalStorage/index.ts +6 -6
- package/src/storages/inLocalStorage/validateCache.ts +4 -4
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +130 -0
- package/src/storages/inMemory/InMemoryStorage.ts +4 -4
- package/src/storages/inMemory/InMemoryStorageCS.ts +5 -5
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +1 -1
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +1 -1
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +3 -3
- package/src/storages/inRedis/{SplitsCacheInRedis.ts → DefinitionsCacheInRedis.ts} +67 -67
- package/src/storages/inRedis/index.ts +2 -2
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +230 -0
- package/src/storages/pluggable/index.ts +2 -2
- package/src/storages/setRolloutPlan.ts +7 -7
- package/src/storages/types.ts +19 -19
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +8 -8
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +3 -3
- package/src/sync/polling/pollingManagerCS.ts +4 -4
- package/src/sync/polling/pollingManagerSS.ts +5 -4
- package/src/sync/polling/syncTasks/definitionsSyncTask.ts +1 -1
- package/src/sync/polling/syncTasks/segmentsSyncTask.ts +3 -4
- package/src/sync/polling/types.ts +1 -1
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +11 -11
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
- package/src/sync/streaming/AuthClient/index.ts +2 -2
- package/src/sync/streaming/AuthClient/types.ts +14 -11
- package/src/sync/streaming/SSEClient/index.ts +2 -2
- package/src/sync/streaming/SSEClient/types.ts +2 -2
- package/src/sync/streaming/SSEHandler/NotificationKeeper.ts +2 -2
- package/src/sync/streaming/SSEHandler/index.ts +2 -2
- package/src/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.ts → DefinitionsUpdateWorker.ts} +16 -16
- package/src/sync/streaming/constants.ts +2 -2
- package/src/sync/streaming/pushManager.ts +16 -16
- package/src/sync/streaming/types.ts +2 -2
- package/src/sync/submitters/telemetrySubmitter.ts +1 -2
- package/src/sync/syncManagerOnline.ts +10 -8
- package/src/utils/Backoff.ts +21 -0
- package/src/utils/inputValidation/trafficTypeExistence.ts +2 -2
- package/src/utils/jwt/types.ts +1 -1
- package/src/utils/lang/sets.ts +4 -2
- package/src/utils/settingsValidation/index.ts +2 -0
- package/src/utils/settingsValidation/url.ts +6 -2
- package/types/splitio.d.ts +27 -8
- package/cjs/storages/AbstractSplitsCacheAsync.js +0 -51
- package/cjs/storages/AbstractSplitsCacheSync.js +0 -76
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -194
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -121
- package/cjs/storages/pluggable/SplitsCachePluggable.js +0 -212
- package/esm/storages/AbstractSplitsCacheAsync.js +0 -48
- package/esm/storages/AbstractSplitsCacheSync.js +0 -71
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -191
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -118
- package/esm/storages/pluggable/SplitsCachePluggable.js +0 -209
- package/src/storages/AbstractSplitsCacheAsync.ts +0 -63
- package/src/storages/AbstractSplitsCacheSync.ts +0 -95
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -234
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -130
- package/src/storages/pluggable/SplitsCachePluggable.ts +0 -230
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { IDefinition } from '../../dtos/types';
|
|
2
|
-
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
|
|
3
|
-
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
4
|
-
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
5
|
-
import { ILogger } from '../../logger/types';
|
|
6
|
-
import { LOG_PREFIX } from './constants';
|
|
7
|
-
import { ISettings } from '../../types';
|
|
8
|
-
import { setToArray } from '../../utils/lang/sets';
|
|
9
|
-
import { StorageAdapter } from '../types';
|
|
10
|
-
|
|
11
|
-
export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
12
|
-
|
|
13
|
-
private readonly keys: KeyBuilderCS;
|
|
14
|
-
private readonly log: ILogger;
|
|
15
|
-
private readonly flagSetsFilter: string[];
|
|
16
|
-
private hasSync?: boolean;
|
|
17
|
-
private readonly storage: StorageAdapter;
|
|
18
|
-
|
|
19
|
-
constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
|
|
20
|
-
super();
|
|
21
|
-
this.keys = keys;
|
|
22
|
-
this.log = settings.log;
|
|
23
|
-
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
24
|
-
this.storage = storage;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private _decrementCount(key: string) {
|
|
28
|
-
const count = toNumber(this.storage.getItem(key)) - 1;
|
|
29
|
-
if (count > 0) this.storage.setItem(key, count + '');
|
|
30
|
-
else this.storage.removeItem(key);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private _decrementCounts(split: IDefinition) {
|
|
34
|
-
try {
|
|
35
|
-
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
36
|
-
this._decrementCount(ttKey);
|
|
37
|
-
|
|
38
|
-
if (usesSegments(split)) {
|
|
39
|
-
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
40
|
-
this._decrementCount(segmentsCountKey);
|
|
41
|
-
}
|
|
42
|
-
} catch (e) {
|
|
43
|
-
this.log.error(LOG_PREFIX + e);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private _incrementCounts(split: IDefinition) {
|
|
48
|
-
try {
|
|
49
|
-
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
50
|
-
this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
|
|
51
|
-
|
|
52
|
-
if (usesSegments(split)) {
|
|
53
|
-
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
54
|
-
this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
|
|
55
|
-
}
|
|
56
|
-
} catch (e) {
|
|
57
|
-
this.log.error(LOG_PREFIX + e);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Removes all splits cache related data from localStorage (splits, counters, changeNumber and lastUpdated).
|
|
64
|
-
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
65
|
-
*/
|
|
66
|
-
clear() {
|
|
67
|
-
// collect item keys
|
|
68
|
-
const len = this.storage.length;
|
|
69
|
-
const accum = [];
|
|
70
|
-
for (let cur = 0; cur < len; cur++) {
|
|
71
|
-
const key = this.storage.key(cur);
|
|
72
|
-
if (key != null && this.keys.isSplitsCacheKey(key)) accum.push(key);
|
|
73
|
-
}
|
|
74
|
-
// remove items
|
|
75
|
-
accum.forEach(key => {
|
|
76
|
-
this.storage.removeItem(key);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
this.hasSync = false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
addSplit(split: IDefinition) {
|
|
83
|
-
const name = split.name;
|
|
84
|
-
const splitKey = this.keys.buildSplitKey(name);
|
|
85
|
-
const splitFromStorage = this.storage.getItem(splitKey);
|
|
86
|
-
const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
|
|
87
|
-
|
|
88
|
-
if (previousSplit) {
|
|
89
|
-
this._decrementCounts(previousSplit);
|
|
90
|
-
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.storage.setItem(splitKey, JSON.stringify(split));
|
|
94
|
-
|
|
95
|
-
this._incrementCounts(split);
|
|
96
|
-
this.addToFlagSets(split);
|
|
97
|
-
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
removeSplit(name: string): boolean {
|
|
102
|
-
const split = this.getSplit(name);
|
|
103
|
-
if (!split) return false;
|
|
104
|
-
|
|
105
|
-
this.storage.removeItem(this.keys.buildSplitKey(name));
|
|
106
|
-
|
|
107
|
-
this._decrementCounts(split);
|
|
108
|
-
this.removeFromFlagSets(split.name, split.sets);
|
|
109
|
-
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getSplit(name: string): IDefinition | null {
|
|
114
|
-
const item = this.storage.getItem(this.keys.buildSplitKey(name));
|
|
115
|
-
return item && JSON.parse(item);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
setChangeNumber(changeNumber: number): boolean {
|
|
119
|
-
try {
|
|
120
|
-
this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
121
|
-
// update "last updated" timestamp with current time
|
|
122
|
-
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
123
|
-
this.hasSync = true;
|
|
124
|
-
return true;
|
|
125
|
-
} catch (e) {
|
|
126
|
-
this.log.error(LOG_PREFIX + e);
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
getChangeNumber(): number {
|
|
132
|
-
const n = -1;
|
|
133
|
-
let value: string | number | null = this.storage.getItem(this.keys.buildSplitsTillKey());
|
|
134
|
-
|
|
135
|
-
if (value !== null) {
|
|
136
|
-
value = parseInt(value, 10);
|
|
137
|
-
|
|
138
|
-
return isNaNNumber(value) ? n : value;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return n;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
getSplitNames(): string[] {
|
|
145
|
-
const len = this.storage.length;
|
|
146
|
-
const accum = [];
|
|
147
|
-
|
|
148
|
-
let cur = 0;
|
|
149
|
-
|
|
150
|
-
while (cur < len) {
|
|
151
|
-
const key = this.storage.key(cur);
|
|
152
|
-
|
|
153
|
-
if (key != null && this.keys.isSplitKey(key)) accum.push(this.keys.extractKey(key));
|
|
154
|
-
|
|
155
|
-
cur++;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return accum;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
trafficTypeExists(trafficType: string): boolean {
|
|
162
|
-
const ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
|
|
163
|
-
return isFiniteNumber(ttCount) && ttCount > 0;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
usesSegments() {
|
|
167
|
-
// If cache hasn't been synchronized with the cloud, assume we need them.
|
|
168
|
-
if (!this.hasSync) return true;
|
|
169
|
-
|
|
170
|
-
const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
171
|
-
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
172
|
-
|
|
173
|
-
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
174
|
-
splitsWithSegmentsCount > 0 :
|
|
175
|
-
true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
179
|
-
return flagSets.map(flagSet => {
|
|
180
|
-
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
181
|
-
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
182
|
-
|
|
183
|
-
return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private addToFlagSets(featureFlag: IDefinition) {
|
|
188
|
-
if (!featureFlag.sets) return;
|
|
189
|
-
|
|
190
|
-
featureFlag.sets.forEach(featureFlagSet => {
|
|
191
|
-
|
|
192
|
-
if (this.flagSetsFilter.length > 0 && !this.flagSetsFilter.some(filterFlagSet => filterFlagSet === featureFlagSet)) return;
|
|
193
|
-
|
|
194
|
-
const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
|
|
195
|
-
|
|
196
|
-
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
197
|
-
|
|
198
|
-
const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
199
|
-
|
|
200
|
-
if (flagSetCache.has(featureFlag.name)) return;
|
|
201
|
-
|
|
202
|
-
flagSetCache.add(featureFlag.name);
|
|
203
|
-
|
|
204
|
-
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private removeFromFlagSets(featureFlagName: string, flagSets?: string[]) {
|
|
209
|
-
if (!flagSets) return;
|
|
210
|
-
|
|
211
|
-
flagSets.forEach(flagSet => {
|
|
212
|
-
this.removeNames(flagSet, featureFlagName);
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private removeNames(flagSetName: string, featureFlagName: string) {
|
|
217
|
-
const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
|
|
218
|
-
|
|
219
|
-
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
220
|
-
|
|
221
|
-
if (!flagSetFromStorage) return;
|
|
222
|
-
|
|
223
|
-
const flagSetCache = new Set(JSON.parse(flagSetFromStorage));
|
|
224
|
-
flagSetCache.delete(featureFlagName);
|
|
225
|
-
|
|
226
|
-
if (flagSetCache.size === 0) {
|
|
227
|
-
this.storage.removeItem(flagSetKey);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { IDefinition, ISplitFiltersValidation } from '../../dtos/types';
|
|
2
|
-
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
|
|
3
|
-
import { isFiniteNumber } from '../../utils/lang';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Default ISplitsCacheSync implementation that stores split definitions in memory.
|
|
7
|
-
*/
|
|
8
|
-
export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
|
|
9
|
-
|
|
10
|
-
private flagSetsFilter: string[];
|
|
11
|
-
private splitsCache: Record<string, IDefinition> = {};
|
|
12
|
-
private ttCache: Record<string, number> = {};
|
|
13
|
-
private changeNumber: number = -1;
|
|
14
|
-
private segmentsCount: number = 0;
|
|
15
|
-
private flagSetsCache: Record<string, Set<string>> = {};
|
|
16
|
-
|
|
17
|
-
constructor(splitFiltersValidation?: ISplitFiltersValidation) {
|
|
18
|
-
super();
|
|
19
|
-
this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
clear() {
|
|
23
|
-
this.splitsCache = {};
|
|
24
|
-
this.ttCache = {};
|
|
25
|
-
this.changeNumber = -1;
|
|
26
|
-
this.segmentsCount = 0;
|
|
27
|
-
this.flagSetsCache = {};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
addSplit(split: IDefinition): boolean {
|
|
31
|
-
const name = split.name;
|
|
32
|
-
const previousSplit = this.getSplit(name);
|
|
33
|
-
if (previousSplit) { // We had this Split already
|
|
34
|
-
|
|
35
|
-
const previousTtName = previousSplit.trafficTypeName;
|
|
36
|
-
this.ttCache[previousTtName]--;
|
|
37
|
-
if (!this.ttCache[previousTtName]) delete this.ttCache[previousTtName];
|
|
38
|
-
|
|
39
|
-
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
40
|
-
|
|
41
|
-
// Subtract from segments count for the previous version of this Split
|
|
42
|
-
if (usesSegments(previousSplit)) this.segmentsCount--;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Store the Split.
|
|
46
|
-
this.splitsCache[name] = split;
|
|
47
|
-
// Update TT cache
|
|
48
|
-
const ttName = split.trafficTypeName;
|
|
49
|
-
this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
|
|
50
|
-
this.addToFlagSets(split);
|
|
51
|
-
|
|
52
|
-
// Add to segments count for the new version of the Split
|
|
53
|
-
if (usesSegments(split)) this.segmentsCount++;
|
|
54
|
-
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
removeSplit(name: string): boolean {
|
|
59
|
-
const split = this.getSplit(name);
|
|
60
|
-
if (!split) return false;
|
|
61
|
-
|
|
62
|
-
// Delete the Split
|
|
63
|
-
delete this.splitsCache[name];
|
|
64
|
-
|
|
65
|
-
const ttName = split.trafficTypeName;
|
|
66
|
-
this.ttCache[ttName]--; // Update tt cache
|
|
67
|
-
if (!this.ttCache[ttName]) delete this.ttCache[ttName];
|
|
68
|
-
this.removeFromFlagSets(split.name, split.sets);
|
|
69
|
-
|
|
70
|
-
// Update the segments count.
|
|
71
|
-
if (usesSegments(split)) this.segmentsCount--;
|
|
72
|
-
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getSplit(name: string): IDefinition | null {
|
|
77
|
-
return this.splitsCache[name] || null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
setChangeNumber(changeNumber: number): boolean {
|
|
81
|
-
this.changeNumber = changeNumber;
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getChangeNumber(): number {
|
|
86
|
-
return this.changeNumber;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getSplitNames(): string[] {
|
|
90
|
-
return Object.keys(this.splitsCache);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
trafficTypeExists(trafficType: string): boolean {
|
|
94
|
-
return isFiniteNumber(this.ttCache[trafficType]) && this.ttCache[trafficType] > 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
usesSegments(): boolean {
|
|
98
|
-
return this.getChangeNumber() === -1 || this.segmentsCount > 0;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
102
|
-
return flagSets.map(flagSet => this.flagSetsCache[flagSet] || new Set());
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private addToFlagSets(featureFlag: IDefinition) {
|
|
106
|
-
if (!featureFlag.sets) return;
|
|
107
|
-
featureFlag.sets.forEach(featureFlagSet => {
|
|
108
|
-
|
|
109
|
-
if (this.flagSetsFilter.length > 0 && !this.flagSetsFilter.some(filterFlagSet => filterFlagSet === featureFlagSet)) return;
|
|
110
|
-
|
|
111
|
-
if (!this.flagSetsCache[featureFlagSet]) this.flagSetsCache[featureFlagSet] = new Set([]);
|
|
112
|
-
|
|
113
|
-
this.flagSetsCache[featureFlagSet].add(featureFlag.name);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private removeFromFlagSets(featureFlagName: string, flagSets: string[] | undefined) {
|
|
118
|
-
if (!flagSets) return;
|
|
119
|
-
flagSets.forEach(flagSet => {
|
|
120
|
-
this.removeNames(flagSet, featureFlagName);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private removeNames(flagSetName: string, featureFlagName: string) {
|
|
125
|
-
if (!this.flagSetsCache[flagSetName]) return;
|
|
126
|
-
this.flagSetsCache[flagSetName].delete(featureFlagName);
|
|
127
|
-
if (this.flagSetsCache[flagSetName].size === 0) delete this.flagSetsCache[flagSetName];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
}
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
|
|
2
|
-
import { KeyBuilder } from '../KeyBuilder';
|
|
3
|
-
import { IPluggableStorageWrapper } from '../types';
|
|
4
|
-
import { ILogger } from '../../logger/types';
|
|
5
|
-
import { IDefinition, ISplitFiltersValidation } from '../../dtos/types';
|
|
6
|
-
import { LOG_PREFIX } from './constants';
|
|
7
|
-
import { AbstractSplitsCacheAsync } from '../AbstractSplitsCacheAsync';
|
|
8
|
-
import { returnDifference } from '../../utils/lang/sets';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* ISplitsCacheAsync implementation for pluggable storages.
|
|
12
|
-
*/
|
|
13
|
-
export class SplitsCachePluggable extends AbstractSplitsCacheAsync {
|
|
14
|
-
|
|
15
|
-
private readonly log: ILogger;
|
|
16
|
-
private readonly keys: KeyBuilder;
|
|
17
|
-
private readonly wrapper: IPluggableStorageWrapper;
|
|
18
|
-
private readonly flagSetsFilter: string[];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a SplitsCache that uses a storage wrapper.
|
|
22
|
-
* @param log - Logger instance.
|
|
23
|
-
* @param keys - Key builder.
|
|
24
|
-
* @param wrapper - Adapted wrapper storage.
|
|
25
|
-
*/
|
|
26
|
-
constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper, splitFiltersValidation?: ISplitFiltersValidation) {
|
|
27
|
-
super();
|
|
28
|
-
this.log = log;
|
|
29
|
-
this.keys = keys;
|
|
30
|
-
this.wrapper = wrapper;
|
|
31
|
-
this.flagSetsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private _decrementCounts(split: IDefinition) {
|
|
35
|
-
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
36
|
-
return this.wrapper.decr(ttKey).then(count => {
|
|
37
|
-
if (count === 0) return this.wrapper.del(ttKey);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private _incrementCounts(split: IDefinition) {
|
|
42
|
-
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
43
|
-
return this.wrapper.incr(ttKey);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private _updateFlagSets(featureFlagName: string, flagSetsOfRemovedFlag?: string[], flagSetsOfAddedFlag?: string[]) {
|
|
47
|
-
const removeFromFlagSets = returnDifference(flagSetsOfRemovedFlag, flagSetsOfAddedFlag);
|
|
48
|
-
|
|
49
|
-
let addToFlagSets = returnDifference(flagSetsOfAddedFlag, flagSetsOfRemovedFlag);
|
|
50
|
-
if (this.flagSetsFilter.length > 0) {
|
|
51
|
-
addToFlagSets = addToFlagSets.filter(flagSet => {
|
|
52
|
-
return this.flagSetsFilter.some(filterFlagSet => filterFlagSet === flagSet);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const items = [featureFlagName];
|
|
57
|
-
|
|
58
|
-
return Promise.all([
|
|
59
|
-
...removeFromFlagSets.map(flagSetName => this.wrapper.removeItems(this.keys.buildFlagSetKey(flagSetName), items)),
|
|
60
|
-
...addToFlagSets.map(flagSetName => this.wrapper.addItems(this.keys.buildFlagSetKey(flagSetName), items))
|
|
61
|
-
]);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Add a given split.
|
|
66
|
-
* The returned promise is resolved when the operation success
|
|
67
|
-
* or rejected if it fails (e.g., wrapper operation fails)
|
|
68
|
-
*/
|
|
69
|
-
addSplit(split: IDefinition): Promise<boolean> {
|
|
70
|
-
const name = split.name;
|
|
71
|
-
const splitKey = this.keys.buildSplitKey(name);
|
|
72
|
-
return this.wrapper.get(splitKey).then(splitFromStorage => {
|
|
73
|
-
|
|
74
|
-
// handling parsing error
|
|
75
|
-
let parsedPreviousSplit: IDefinition, stringifiedNewSplit;
|
|
76
|
-
try {
|
|
77
|
-
parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined;
|
|
78
|
-
stringifiedNewSplit = JSON.stringify(split);
|
|
79
|
-
} catch (e) {
|
|
80
|
-
throw new Error('Error parsing feature flag definition: ' + e);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return this.wrapper.set(splitKey, stringifiedNewSplit).then(() => {
|
|
84
|
-
// avoid unnecessary increment/decrement operations
|
|
85
|
-
if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName) return;
|
|
86
|
-
|
|
87
|
-
// update traffic type counts
|
|
88
|
-
return this._incrementCounts(split).then(() => {
|
|
89
|
-
if (parsedPreviousSplit) return this._decrementCounts(parsedPreviousSplit);
|
|
90
|
-
});
|
|
91
|
-
}).then(() => this._updateFlagSets(name, parsedPreviousSplit && parsedPreviousSplit.sets, split.sets));
|
|
92
|
-
}).then(() => true);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Remove a given split.
|
|
97
|
-
* The returned promise is resolved when the operation success, with a boolean indicating if the split existed or not.
|
|
98
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
99
|
-
*/
|
|
100
|
-
removeSplit(name: string) {
|
|
101
|
-
return this.getSplit(name).then((split) => {
|
|
102
|
-
if (split) {
|
|
103
|
-
return this._decrementCounts(split).then(() => this._updateFlagSets(name, split.sets));
|
|
104
|
-
}
|
|
105
|
-
}).then(() => {
|
|
106
|
-
return this.wrapper.del(this.keys.buildSplitKey(name));
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get split.
|
|
112
|
-
* The returned promise is resolved with the split definition or null if it's not defined,
|
|
113
|
-
* or rejected if wrapper operation fails.
|
|
114
|
-
*/
|
|
115
|
-
getSplit(name: string): Promise<IDefinition | null> {
|
|
116
|
-
return this.wrapper.get(this.keys.buildSplitKey(name))
|
|
117
|
-
.then(maybeSplit => maybeSplit && JSON.parse(maybeSplit));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get list of splits.
|
|
122
|
-
* The returned promise is resolved with a map of split names to their split definition or null if it's not defined,
|
|
123
|
-
* or rejected if wrapper operation fails.
|
|
124
|
-
*/
|
|
125
|
-
getSplits(names: string[]): Promise<Record<string, IDefinition | null>> {
|
|
126
|
-
const keys = names.map(name => this.keys.buildSplitKey(name));
|
|
127
|
-
|
|
128
|
-
return this.wrapper.getMany(keys).then(splitDefinitions => {
|
|
129
|
-
const splits: Record<string, IDefinition | null> = {};
|
|
130
|
-
names.forEach((name, idx) => {
|
|
131
|
-
const split = splitDefinitions[idx];
|
|
132
|
-
splits[name] = split && JSON.parse(split);
|
|
133
|
-
});
|
|
134
|
-
return Promise.resolve(splits);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get list of all split definitions.
|
|
140
|
-
* The returned promise is resolved with the list of split definitions,
|
|
141
|
-
* or rejected if wrapper operation fails.
|
|
142
|
-
*/
|
|
143
|
-
getAll(): Promise<IDefinition[]> {
|
|
144
|
-
return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix())
|
|
145
|
-
.then((listOfKeys) => this.wrapper.getMany(listOfKeys))
|
|
146
|
-
.then((splitDefinitions) => splitDefinitions.map((splitDefinition) => {
|
|
147
|
-
return JSON.parse(splitDefinition as string);
|
|
148
|
-
}));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Get list of split names.
|
|
153
|
-
* The returned promise is resolved with the list of split names,
|
|
154
|
-
* or rejected if wrapper operation fails.
|
|
155
|
-
*/
|
|
156
|
-
getSplitNames(): Promise<string[]> {
|
|
157
|
-
return this.wrapper.getKeysByPrefix(this.keys.buildSplitKeyPrefix()).then(
|
|
158
|
-
(listOfKeys) => listOfKeys.map(this.keys.extractKey)
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get list of feature flag names related to a given list of flag set names.
|
|
164
|
-
* The returned promise is resolved with the list of feature flag names per flag set.
|
|
165
|
-
* It never rejects (If there is a wrapper error for some flag set, an empty set is returned for it).
|
|
166
|
-
*/
|
|
167
|
-
getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]> {
|
|
168
|
-
return Promise.all(flagSets.map(flagSet => {
|
|
169
|
-
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
170
|
-
return this.wrapper.getItems(flagSetKey).catch(() => []);
|
|
171
|
-
})).then(namesByFlagSets => namesByFlagSets.map(namesByFlagSet => new Set(namesByFlagSet)));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check traffic type existence.
|
|
176
|
-
* The returned promise is resolved with a boolean indicating whether the TT exist or not.
|
|
177
|
-
* In case of wrapper operation failures, the promise resolves with a true value, assuming that the TT might exist.
|
|
178
|
-
* It will never be rejected.
|
|
179
|
-
*/
|
|
180
|
-
trafficTypeExists(trafficType: string): Promise<boolean> {
|
|
181
|
-
// If there is a number there should be > 0, otherwise the TT is considered as not existent.
|
|
182
|
-
return this.wrapper.get(this.keys.buildTrafficTypeKey(trafficType))
|
|
183
|
-
.then((ttCount: string | null | number) => {
|
|
184
|
-
if (ttCount === null) return false; // if entry doesn't exist, means that TT doesn't exist
|
|
185
|
-
|
|
186
|
-
ttCount = parseInt(ttCount as string, 10);
|
|
187
|
-
if (!isFiniteNumber(ttCount) || ttCount < 0) {
|
|
188
|
-
this.log.info(LOG_PREFIX + `Could not validate traffic type existence of ${trafficType} due to data corruption of some sorts.`);
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return ttCount > 0;
|
|
193
|
-
}).catch(e => {
|
|
194
|
-
this.log.error(LOG_PREFIX + `Could not validate traffic type existence of ${trafficType} due to an error: ${e}.`);
|
|
195
|
-
// If there is an error, bypass the validation so the event can get tracked.
|
|
196
|
-
return true;
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Set till number.
|
|
202
|
-
* The returned promise is resolved when the operation success,
|
|
203
|
-
* or rejected if it fails (e.g., wrapper operation fails).
|
|
204
|
-
*/
|
|
205
|
-
setChangeNumber(changeNumber: number) {
|
|
206
|
-
return this.wrapper.set(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get till number or -1 if it's not defined.
|
|
211
|
-
* The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.
|
|
212
|
-
* The promise will never be rejected.
|
|
213
|
-
*/
|
|
214
|
-
getChangeNumber(): Promise<number> {
|
|
215
|
-
return this.wrapper.get(this.keys.buildSplitsTillKey()).then((value) => {
|
|
216
|
-
const i = parseInt(value as string, 10);
|
|
217
|
-
|
|
218
|
-
return isNaNNumber(i) ? -1 : i;
|
|
219
|
-
}).catch((e) => {
|
|
220
|
-
this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e);
|
|
221
|
-
return -1;
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// @TODO implement if required by DataLoader or producer mode
|
|
226
|
-
clear() {
|
|
227
|
-
return Promise.resolve(true);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
}
|