@splitsoftware/splitio-commons 3.1.1-rc.12 → 3.1.1-rc.13
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 -0
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
- package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +9 -7
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
- package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
- package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -11
- package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +1 -1
- package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
- package/esm/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +9 -7
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
- package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
- package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +8 -11
- package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +2 -2
- package/package.json +1 -1
- package/src/storages/AbstractDefinitionsCacheAsync.ts +3 -2
- package/src/storages/AbstractDefinitionsCacheSync.ts +3 -3
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +3 -1
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +5 -3
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +1 -0
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -3
- package/src/storages/inRedis/DefinitionsCacheInRedis.ts +9 -7
- package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +20 -11
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +10 -7
- package/src/storages/pluggable/RBSegmentsCachePluggable.ts +20 -11
- package/src/storages/types.ts +6 -6
- package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
- package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
- package/src/sync/offline/splitsParser/types.ts +7 -3
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +9 -13
- package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +2 -2
- package/src/sync/syncManagerOnline.ts +0 -1
|
@@ -36,23 +36,32 @@ export class RBSegmentsCachePluggable implements IRBSegmentsCacheAsync {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<
|
|
39
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<string[]> {
|
|
40
40
|
return Promise.all([
|
|
41
41
|
this.setChangeNumber(changeNumber),
|
|
42
|
-
Promise.all(toAdd.map(
|
|
43
|
-
|
|
44
|
-
const stringifiedNewRBSegment = JSON.stringify(toAdd);
|
|
45
|
-
return this.wrapper.set(key, stringifiedNewRBSegment).then(() => true);
|
|
46
|
-
})),
|
|
47
|
-
Promise.all(toRemove.map(toRemove => {
|
|
48
|
-
const key = this.keys.buildRBSegmentKey(toRemove);
|
|
49
|
-
return this.wrapper.del(key);
|
|
50
|
-
}))
|
|
42
|
+
Promise.all(toAdd.map(rbSegment => this.add(rbSegment))),
|
|
43
|
+
Promise.all(toRemove.map(name => this.remove(name)))
|
|
51
44
|
]).then(([, added, removed]) => {
|
|
52
|
-
return
|
|
45
|
+
return toRemove.filter((_, i) => removed[i])
|
|
46
|
+
.concat(toAdd.filter((_, i) => added[i]).map(rbSegment => rbSegment.name));
|
|
53
47
|
});
|
|
54
48
|
}
|
|
55
49
|
|
|
50
|
+
private add(rbSegment: IRBSegment): Promise<boolean> {
|
|
51
|
+
const key = this.keys.buildRBSegmentKey(rbSegment.name);
|
|
52
|
+
return this.get(rbSegment.name).then(previous => {
|
|
53
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
54
|
+
|
|
55
|
+
const stringifiedNewRBSegment = JSON.stringify(rbSegment);
|
|
56
|
+
return this.wrapper.set(key, stringifiedNewRBSegment).then(() => true);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private remove(name: string): Promise<boolean> {
|
|
61
|
+
const key = this.keys.buildRBSegmentKey(name);
|
|
62
|
+
return this.wrapper.del(key);
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
setChangeNumber(changeNumber?: number) {
|
|
57
66
|
if (changeNumber !== undefined) {
|
|
58
67
|
return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
package/src/storages/types.ts
CHANGED
|
@@ -195,7 +195,7 @@ export interface IPluggableStorageWrapper {
|
|
|
195
195
|
/** Splits cache */
|
|
196
196
|
|
|
197
197
|
export interface IDefinitionsCacheBase {
|
|
198
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): MaybeThenable<
|
|
198
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): MaybeThenable<string[]>,
|
|
199
199
|
get(name: string): MaybeThenable<IDefinition | null>,
|
|
200
200
|
getMany(names: string[]): MaybeThenable<Record<string, IDefinition | null>>, // `fetchMany` in spec
|
|
201
201
|
// should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
|
|
@@ -212,7 +212,7 @@ export interface IDefinitionsCacheBase {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
export interface IDefinitionsCacheSync extends IDefinitionsCacheBase {
|
|
215
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number):
|
|
215
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): string[],
|
|
216
216
|
get(name: string): IDefinition | null,
|
|
217
217
|
getMany(names: string[]): Record<string, IDefinition | null>,
|
|
218
218
|
getChangeNumber(): number,
|
|
@@ -226,7 +226,7 @@ export interface IDefinitionsCacheSync extends IDefinitionsCacheBase {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
export interface IDefinitionsCacheAsync extends IDefinitionsCacheBase {
|
|
229
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): Promise<
|
|
229
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): Promise<string[]>,
|
|
230
230
|
get(name: string): Promise<IDefinition | null>,
|
|
231
231
|
getMany(names: string[]): Promise<Record<string, IDefinition | null>>,
|
|
232
232
|
getChangeNumber(): Promise<number>,
|
|
@@ -242,7 +242,7 @@ export interface IDefinitionsCacheAsync extends IDefinitionsCacheBase {
|
|
|
242
242
|
/** Rule-Based Segments cache */
|
|
243
243
|
|
|
244
244
|
export interface IRBSegmentsCacheBase {
|
|
245
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): MaybeThenable<
|
|
245
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): MaybeThenable<string[]>,
|
|
246
246
|
get(name: string): MaybeThenable<IRBSegment | null>,
|
|
247
247
|
getChangeNumber(): MaybeThenable<number>,
|
|
248
248
|
clear(): MaybeThenable<boolean | void>,
|
|
@@ -250,7 +250,7 @@ export interface IRBSegmentsCacheBase {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
|
|
253
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number):
|
|
253
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[],
|
|
254
254
|
get(name: string): IRBSegment | null,
|
|
255
255
|
getChangeNumber(): number,
|
|
256
256
|
getAll(): IRBSegment[],
|
|
@@ -261,7 +261,7 @@ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
|
|
264
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<
|
|
264
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<string[]>,
|
|
265
265
|
get(name: string): Promise<IRBSegment | null>,
|
|
266
266
|
getChangeNumber(): Promise<number>,
|
|
267
267
|
clear(): Promise<boolean | void>,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { IDefinition, IDefinitionChangesResponse } from '../../../dtos/types';
|
|
2
|
+
import { CONTROL } from '../../../utils/constants';
|
|
3
|
+
import { IDefinitionPartial } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory of a stateful function that, given the current mocked definitions, returns the changes since the previous call
|
|
7
|
+
* as an `IDefinitionChangesResponse`, i.e., the full definitions that were added or modified, and the names of the removed ones.
|
|
8
|
+
* Definitions are compared by value. The response `till` is an incrementing value based on the current time.
|
|
9
|
+
* Definitions have no `changeNumber`, so storages never consider them outdated.
|
|
10
|
+
*
|
|
11
|
+
* The response has no `d` property if there are no changes, except for the first call, which reports the (possibly empty) mock.
|
|
12
|
+
* It is meant to be used by `IDefinitionsParser` implementations, such as `splitsParserFromSettings`.
|
|
13
|
+
*/
|
|
14
|
+
export function definitionChangesBuilderFactory() {
|
|
15
|
+
|
|
16
|
+
let previous: Record<string, string> | undefined; // serialized definitions of the last reported mock, by name
|
|
17
|
+
let lastTill = 0;
|
|
18
|
+
|
|
19
|
+
return function definitionChangesBuilder(mock: Record<string, IDefinitionPartial>): IDefinitionChangesResponse {
|
|
20
|
+
const current: Record<string, string> = {};
|
|
21
|
+
const updatedNames = Object.keys(mock).reduce((updatedNames: string[], name) => {
|
|
22
|
+
current[name] = JSON.stringify(mock[name]);
|
|
23
|
+
if (!previous || previous[name] !== current[name]) updatedNames.push(name);
|
|
24
|
+
return updatedNames;
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const removed = previous ? Object.keys(previous).filter(name => current[name] === undefined) : [];
|
|
28
|
+
|
|
29
|
+
// Only the first call reports an empty change
|
|
30
|
+
if (previous && updatedNames.length === 0 && removed.length === 0) return {};
|
|
31
|
+
|
|
32
|
+
previous = current;
|
|
33
|
+
|
|
34
|
+
// Incrementing `till` based on the current time
|
|
35
|
+
const till = lastTill = Math.max(Date.now(), lastTill + 1);
|
|
36
|
+
|
|
37
|
+
const updated = updatedNames.map(name => ({
|
|
38
|
+
name,
|
|
39
|
+
status: 'ACTIVE',
|
|
40
|
+
killed: false,
|
|
41
|
+
trafficAllocation: 100,
|
|
42
|
+
defaultTreatment: CONTROL, // `seed` is undefined in localhost mode
|
|
43
|
+
...mock[name]
|
|
44
|
+
} as IDefinition));
|
|
45
|
+
|
|
46
|
+
return { d: { updated, removed, till } };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
}
|
|
@@ -1,52 +1,22 @@
|
|
|
1
1
|
import { IDefinitionPartial } from './types';
|
|
2
|
+
import { IDefinitionChangesResponse } from '../../../dtos/types';
|
|
2
3
|
import SplitIO from '../../../../types/splitio';
|
|
3
|
-
import { isObject, forOwn
|
|
4
|
+
import { isObject, forOwn } from '../../../utils/lang';
|
|
4
5
|
import { parseCondition } from './parseCondition';
|
|
5
|
-
|
|
6
|
-
function hasTreatmentChanged(prev: string | SplitIO.TreatmentWithConfig, curr: string | SplitIO.TreatmentWithConfig) {
|
|
7
|
-
if (typeof prev !== typeof curr) return true;
|
|
8
|
-
|
|
9
|
-
if (typeof prev === 'string') { // strings treatments, just compare
|
|
10
|
-
return prev !== curr;
|
|
11
|
-
} else { // has treatment and config, compare both
|
|
12
|
-
return prev.treatment !== (curr as SplitIO.TreatmentWithConfig).treatment || prev.config !== (curr as SplitIO.TreatmentWithConfig).config;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
6
|
+
import { definitionChangesBuilderFactory } from './definitionChangesBuilder';
|
|
15
7
|
|
|
16
8
|
export function splitsParserFromSettingsFactory() {
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function mockUpdated(currentData: SplitIO.MockedFeaturesMap) {
|
|
21
|
-
const names = Object.keys(currentData);
|
|
22
|
-
|
|
23
|
-
// Different amount of items
|
|
24
|
-
if (names.length !== Object.keys(previousMock).length) {
|
|
25
|
-
previousMock = merge({}, currentData) as SplitIO.MockedFeaturesMap;
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return names.some(name => {
|
|
30
|
-
const newSplit = !previousMock[name];
|
|
31
|
-
const newTreatment = hasTreatmentChanged(previousMock[name], currentData[name]);
|
|
32
|
-
const changed = newSplit || newTreatment;
|
|
33
|
-
|
|
34
|
-
if (changed) previousMock = merge({}, currentData) as SplitIO.MockedFeaturesMap;
|
|
35
|
-
|
|
36
|
-
return changed;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
10
|
+
const definitionChangesBuilder = definitionChangesBuilderFactory();
|
|
39
11
|
|
|
40
12
|
/**
|
|
41
13
|
*
|
|
42
14
|
* @param settings - validated object with mocked features mapping.
|
|
43
15
|
*/
|
|
44
|
-
return function splitsParserFromSettings(settings: Pick<SplitIO.ISettings, 'features'>):
|
|
16
|
+
return function splitsParserFromSettings(settings: Pick<SplitIO.ISettings, 'features'>): IDefinitionChangesResponse {
|
|
45
17
|
const features = settings.features as SplitIO.MockedFeaturesMap || {};
|
|
46
18
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const splitObjects: Record<string, IDefinitionPartial> = {};
|
|
19
|
+
const mock: Record<string, IDefinitionPartial> = {};
|
|
50
20
|
|
|
51
21
|
forOwn(features, (data, splitName) => {
|
|
52
22
|
let treatment = data;
|
|
@@ -59,14 +29,14 @@ export function splitsParserFromSettingsFactory() {
|
|
|
59
29
|
const configurations: Record<string, string> = {};
|
|
60
30
|
if (config !== null) configurations[treatment as string] = config;
|
|
61
31
|
|
|
62
|
-
|
|
32
|
+
mock[splitName] = {
|
|
63
33
|
trafficTypeName: 'localhost',
|
|
64
34
|
conditions: [parseCondition({ treatment: treatment as string })],
|
|
65
35
|
configurations
|
|
66
36
|
};
|
|
67
37
|
});
|
|
68
38
|
|
|
69
|
-
return
|
|
39
|
+
return definitionChangesBuilder(mock);
|
|
70
40
|
};
|
|
71
41
|
|
|
72
42
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { IDefinition } from '../../../dtos/types';
|
|
1
|
+
import { IDefinition, IDefinitionChangesResponse } from '../../../dtos/types';
|
|
2
2
|
import { ISettings } from '../../../types';
|
|
3
3
|
|
|
4
4
|
// Split definition used in offline mode
|
|
5
5
|
export type IDefinitionPartial = Pick<IDefinition, 'conditions' | 'configurations' | 'trafficTypeName'>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Analog to `IDefinitionChangesFetcher` used by `definitionChangesUpdaterFactory`.
|
|
9
|
+
* It returns the definitions changes since the last call, with an incrementing `till`.
|
|
10
|
+
* The response has no `d` property if there are no changes, e.g., if the mocked definitions were not modified since the last call.
|
|
11
|
+
*/
|
|
12
|
+
export type IDefinitionsParser = (settings: ISettings) => IDefinitionChangesResponse
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { forOwn } from '../../../utils/lang';
|
|
2
1
|
import { IReadinessManager } from '../../../readiness/types';
|
|
3
2
|
import { IStorageSync } from '../../../storages/types';
|
|
4
|
-
import { IDefinitionsParser
|
|
5
|
-
import {
|
|
3
|
+
import { IDefinitionsParser } from '../splitsParser/types';
|
|
4
|
+
import { IDefinitionChangesResponse } from '../../../dtos/types';
|
|
6
5
|
import { syncTaskFactory } from '../../syncTask';
|
|
7
6
|
import { ISyncTask } from '../../types';
|
|
8
7
|
import { ISettings } from '../../../types';
|
|
9
|
-
import { CONTROL } from '../../../utils/constants';
|
|
10
8
|
import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, FLAGS_UPDATE, SEGMENTS_UPDATE } from '../../../readiness/constants';
|
|
11
9
|
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
|
|
12
10
|
|
|
@@ -24,55 +22,37 @@ export function fromObjectUpdaterFactory(
|
|
|
24
22
|
let startingUp = true;
|
|
25
23
|
|
|
26
24
|
return function objectUpdater() {
|
|
27
|
-
|
|
28
|
-
let loadError = null;
|
|
29
|
-
let splitsMock: false | Record<string, IDefinitionPartial> = {};
|
|
25
|
+
let changes: IDefinitionChangesResponse | undefined;
|
|
30
26
|
try {
|
|
31
|
-
|
|
27
|
+
changes = splitsParser(settings);
|
|
32
28
|
} catch (err) {
|
|
33
|
-
loadError = err;
|
|
34
29
|
log.error(ERROR_SYNC_OFFLINE_LOADING, [err]);
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
if (!
|
|
38
|
-
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
|
|
32
|
+
if (!changes || !changes.d) return Promise.resolve(true);
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
splits.push({
|
|
43
|
-
name,
|
|
44
|
-
status: 'ACTIVE',
|
|
45
|
-
killed: false,
|
|
46
|
-
trafficAllocation: 100,
|
|
47
|
-
defaultTreatment: CONTROL,
|
|
48
|
-
conditions: val.conditions || [],
|
|
49
|
-
configurations: val.configurations,
|
|
50
|
-
trafficTypeName: val.trafficTypeName
|
|
51
|
-
});
|
|
52
|
-
});
|
|
34
|
+
const { updated, removed, till } = changes.d;
|
|
35
|
+
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(changes.d)]);
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
// On start, clear definitions that might be in the storage from a previous session (InLocalStorage), since the parser only tracks its own changes.
|
|
38
|
+
return Promise.resolve(startingUp && definitions.clear()).then(() => {
|
|
39
|
+
return definitions.update(updated, removed, till);
|
|
40
|
+
}).then((names) => {
|
|
41
|
+
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names });
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} else {
|
|
74
|
-
return Promise.resolve(true);
|
|
75
|
-
}
|
|
43
|
+
if (startingUp) {
|
|
44
|
+
startingUp = false;
|
|
45
|
+
Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then((cacheMetadata) => {
|
|
46
|
+
// Emits SDK_READY_FROM_CACHE
|
|
47
|
+
if (!cacheMetadata.initialCacheLoad) {
|
|
48
|
+
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
|
|
49
|
+
}
|
|
50
|
+
// Emits SDK_READY
|
|
51
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
76
56
|
};
|
|
77
57
|
}
|
|
78
58
|
|
|
@@ -48,7 +48,6 @@ export function parseSegments(ruleEntity: IDefinition | IRBSegment, matcherType:
|
|
|
48
48
|
interface IDefinitionMutations<T extends IDefinition | IRBSegment> {
|
|
49
49
|
updated: T[],
|
|
50
50
|
removed: string[],
|
|
51
|
-
names: string[]
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
/**
|
|
@@ -88,9 +87,8 @@ export function computeMutation<T extends IDefinition | IRBSegment>(update: { up
|
|
|
88
87
|
} else {
|
|
89
88
|
accum.removed.push(ruleEntity.name);
|
|
90
89
|
}
|
|
91
|
-
accum.names.push(ruleEntity.name);
|
|
92
90
|
return accum;
|
|
93
|
-
}, { updated: [], removed: update.removed
|
|
91
|
+
}, { updated: [], removed: update.removed } as IDefinitionMutations<T>);
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
function convertInstantUpdateToDefinitionChanges(instantUpdate: InstantUpdate) {
|
|
@@ -173,33 +171,31 @@ export function definitionChangesUpdaterFactory(
|
|
|
173
171
|
.then((definitionChanges: IDefinitionChangesResponse) => {
|
|
174
172
|
const usedSegments = new Set<string>();
|
|
175
173
|
|
|
176
|
-
let updatedDefinitions: string[] = [];
|
|
177
|
-
let ffUpdate: MaybeThenable<boolean> = false;
|
|
174
|
+
let updatedDefinitions: MaybeThenable<string[]> = [];
|
|
178
175
|
if (definitionChanges.d) {
|
|
179
|
-
const { updated, removed
|
|
180
|
-
updatedDefinitions = names;
|
|
176
|
+
const { updated, removed } = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation);
|
|
181
177
|
log.debug(SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
|
|
182
|
-
|
|
178
|
+
updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
|
|
183
179
|
}
|
|
184
180
|
|
|
185
|
-
let
|
|
181
|
+
let updatedRBSNames: MaybeThenable<string[]> = [];
|
|
186
182
|
if (definitionChanges.rbs) {
|
|
187
183
|
const { updated, removed } = computeMutation(definitionChanges.rbs, usedSegments);
|
|
188
184
|
log.debug(SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
|
|
189
|
-
|
|
185
|
+
updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
|
|
190
186
|
}
|
|
191
187
|
|
|
192
|
-
return Promise.all([
|
|
188
|
+
return Promise.all([updatedDefinitions, updatedRBSNames,
|
|
193
189
|
// @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
|
|
194
190
|
segments.registerSegments(setToArray(usedSegments))
|
|
195
|
-
]).then(([
|
|
191
|
+
]).then(([updatedDefinitions, updatedRBSNames]) => {
|
|
196
192
|
if (storage.save) storage.save();
|
|
197
193
|
|
|
198
194
|
startingUp = false;
|
|
199
195
|
|
|
200
196
|
if (definitionsEventEmitter) {
|
|
201
197
|
// To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
|
|
202
|
-
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((
|
|
198
|
+
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((updatedDefinitions.length > 0 || updatedRBSNames.length > 0) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
|
|
203
199
|
.catch(() => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
|
|
204
200
|
.then(emitSplitsArrivedEvent => {
|
|
205
201
|
// emit SDK events
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IRBSegment, IDefinition } from '../../../dtos/types';
|
|
2
2
|
import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
|
|
3
|
-
import { SDK_DEFINITIONS_ARRIVED } from '../../../readiness/constants';
|
|
3
|
+
import { SDK_DEFINITIONS_ARRIVED, FLAGS_UPDATE } from '../../../readiness/constants';
|
|
4
4
|
import { ISdkFactoryContextSync } from '../../../sdkFactory/types';
|
|
5
5
|
import { IRBSegmentsCacheSync, IDefinitionsCacheSync } from '../../../storages/types';
|
|
6
6
|
import { Backoff } from '../../../utils/Backoff';
|
|
@@ -133,7 +133,7 @@ export function DefinitionsUpdateWorker(
|
|
|
133
133
|
killDefinition({ changeNumber, splitName, defaultTreatment }: ISplitKillData) {
|
|
134
134
|
if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
|
|
135
135
|
// trigger an SDK_UPDATE if Split was killed locally
|
|
136
|
-
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, true);
|
|
136
|
+
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [splitName] }, true /* isSplitKill */);
|
|
137
137
|
}
|
|
138
138
|
// queues the SplitChanges fetch (only if changeNumber is newer)
|
|
139
139
|
ff.put({ changeNumber } as ISplitUpdateData);
|