@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.
Files changed (50) hide show
  1. package/CHANGES.txt +2 -0
  2. package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
  3. package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
  4. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
  5. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
  6. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
  7. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
  8. package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
  9. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
  10. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +9 -7
  11. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
  12. package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
  13. package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
  14. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
  15. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -11
  16. package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +1 -1
  17. package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
  18. package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
  19. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -0
  20. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +4 -2
  21. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -0
  22. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -2
  23. package/esm/storages/inRedis/DefinitionsCacheInRedis.js +9 -7
  24. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +18 -10
  25. package/esm/storages/pluggable/DefinitionsCachePluggable.js +9 -7
  26. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +18 -10
  27. package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
  28. package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
  29. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
  30. package/esm/sync/polling/updaters/definitionChangesUpdater.js +8 -11
  31. package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +2 -2
  32. package/package.json +1 -1
  33. package/src/storages/AbstractDefinitionsCacheAsync.ts +3 -2
  34. package/src/storages/AbstractDefinitionsCacheSync.ts +3 -3
  35. package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +3 -1
  36. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +5 -3
  37. package/src/storages/inMemory/DefinitionsCacheInMemory.ts +1 -0
  38. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -3
  39. package/src/storages/inRedis/DefinitionsCacheInRedis.ts +9 -7
  40. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +20 -11
  41. package/src/storages/pluggable/DefinitionsCachePluggable.ts +10 -7
  42. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +20 -11
  43. package/src/storages/types.ts +6 -6
  44. package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
  45. package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
  46. package/src/sync/offline/splitsParser/types.ts +7 -3
  47. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
  48. package/src/sync/polling/updaters/definitionChangesUpdater.ts +9 -13
  49. package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +2 -2
  50. 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<boolean> {
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(toAdd => {
43
- const key = this.keys.buildRBSegmentKey(toAdd.name);
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 added.some(result => result) || removed.some(result => result);
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 + '');
@@ -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<boolean>,
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): boolean,
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<boolean>,
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<boolean>,
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): boolean,
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<boolean>,
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, merge } from '../../../utils/lang';
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
- let previousMock: SplitIO.MockedFeaturesMap = { 'emptyMock': '1' };
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'>): false | Record<string, IDefinitionPartial> {
16
+ return function splitsParserFromSettings(settings: Pick<SplitIO.ISettings, 'features'>): IDefinitionChangesResponse {
45
17
  const features = settings.features as SplitIO.MockedFeaturesMap || {};
46
18
 
47
- if (!mockUpdated(features)) return false;
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
- splitObjects[splitName] = {
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 splitObjects;
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
- // Analog to `IDefinitionChangesFetcher` used by `definitionChangesUpdaterFactory`
8
- export type IDefinitionsParser = (settings: ISettings) => false | Record<string, IDefinitionPartial>
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, IDefinitionPartial } from '../splitsParser/types';
5
- import { IDefinition } from '../../../dtos/types';
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
- const splits: IDefinition[] = [];
28
- let loadError = null;
29
- let splitsMock: false | Record<string, IDefinitionPartial> = {};
25
+ let changes: IDefinitionChangesResponse | undefined;
30
26
  try {
31
- splitsMock = splitsParser(settings);
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 (!loadError && splitsMock) {
38
- log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
32
+ if (!changes || !changes.d) return Promise.resolve(true);
39
33
 
40
- forOwn(splitsMock, (val, name) => {
41
- // @ts-ignore Split changeNumber and seed is undefined in localhost mode
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
- return Promise.all([
55
- definitions.clear(), // required to sync removed splits from mock
56
- definitions.update(splits, [], Date.now())
57
- ]).then(() => {
58
- readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [] });
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
- if (startingUp) {
61
- startingUp = false;
62
- Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then((cacheMetadata) => {
63
- // Emits SDK_READY_FROM_CACHE
64
- if (!cacheMetadata.initialCacheLoad) {
65
- readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
66
- }
67
- // Emits SDK_READY
68
- readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
69
- });
70
- }
71
- return true;
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, names: update.removed.slice() } as IDefinitionMutations<T>);
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, names } = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation);
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
- ffUpdate = definitions.update(updated, removed, definitionChanges.d.till);
178
+ updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
183
179
  }
184
180
 
185
- let rbsUpdate: MaybeThenable<boolean> = false;
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
- rbsUpdate = rbSegments.update(updated, removed, definitionChanges.rbs.till);
185
+ updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
190
186
  }
191
187
 
192
- return Promise.all([ffUpdate, rbsUpdate,
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(([ffChanged, rbsChanged]) => {
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 || ((ffChanged || rbsChanged) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
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);
@@ -100,7 +100,6 @@ export function syncManagerOnlineFactory(
100
100
  if (!cacheMetadata.initialCacheLoad) {
101
101
  readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
102
102
  }
103
-
104
103
  }
105
104
 
106
105
  // start syncing splits and segments