@splitsoftware/splitio-commons 2.1.0 → 2.1.1-rc.0

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 (154) hide show
  1. package/README.md +1 -0
  2. package/cjs/evaluator/combiners/and.js +2 -6
  3. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  4. package/cjs/evaluator/condition/index.js +6 -5
  5. package/cjs/evaluator/index.js +7 -7
  6. package/cjs/evaluator/matchers/index.js +3 -1
  7. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  8. package/cjs/evaluator/matchers/rbsegment.js +43 -0
  9. package/cjs/evaluator/matchersTransform/index.js +4 -0
  10. package/cjs/evaluator/parser/index.js +2 -2
  11. package/cjs/evaluator/value/sanitize.js +1 -0
  12. package/cjs/logger/constants.js +5 -6
  13. package/cjs/logger/messages/debug.js +3 -4
  14. package/cjs/logger/messages/warn.js +1 -1
  15. package/cjs/services/splitApi.js +2 -2
  16. package/cjs/storages/AbstractSplitsCacheAsync.js +12 -1
  17. package/cjs/storages/AbstractSplitsCacheSync.js +10 -9
  18. package/cjs/storages/KeyBuilder.js +8 -15
  19. package/cjs/storages/KeyBuilderCS.js +12 -3
  20. package/cjs/storages/KeyBuilderSS.js +3 -0
  21. package/cjs/storages/dataLoader.js +1 -2
  22. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  23. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +14 -16
  24. package/cjs/storages/inLocalStorage/index.js +4 -0
  25. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  26. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  27. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  28. package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
  29. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  30. package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
  31. package/cjs/storages/inRedis/index.js +2 -0
  32. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  33. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
  34. package/cjs/storages/pluggable/index.js +2 -0
  35. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
  36. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  37. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  38. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  39. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  40. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  41. package/cjs/sync/polling/updaters/splitChangesUpdater.js +53 -51
  42. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  43. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  44. package/cjs/sync/streaming/constants.js +2 -1
  45. package/cjs/sync/streaming/pushManager.js +3 -16
  46. package/cjs/sync/syncManagerOnline.js +2 -2
  47. package/cjs/utils/constants/index.js +3 -2
  48. package/esm/evaluator/combiners/and.js +2 -6
  49. package/esm/evaluator/combiners/ifelseif.js +7 -7
  50. package/esm/evaluator/condition/index.js +6 -5
  51. package/esm/evaluator/index.js +7 -7
  52. package/esm/evaluator/matchers/index.js +3 -1
  53. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  54. package/esm/evaluator/matchers/rbsegment.js +39 -0
  55. package/esm/evaluator/matchersTransform/index.js +4 -0
  56. package/esm/evaluator/parser/index.js +2 -2
  57. package/esm/evaluator/value/sanitize.js +1 -0
  58. package/esm/logger/constants.js +2 -3
  59. package/esm/logger/messages/debug.js +3 -4
  60. package/esm/logger/messages/warn.js +1 -1
  61. package/esm/services/splitApi.js +2 -2
  62. package/esm/storages/AbstractSplitsCacheAsync.js +12 -1
  63. package/esm/storages/AbstractSplitsCacheSync.js +10 -9
  64. package/esm/storages/KeyBuilder.js +8 -15
  65. package/esm/storages/KeyBuilderCS.js +12 -3
  66. package/esm/storages/KeyBuilderSS.js +3 -0
  67. package/esm/storages/dataLoader.js +1 -2
  68. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  69. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +14 -16
  70. package/esm/storages/inLocalStorage/index.js +4 -0
  71. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  72. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  73. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  74. package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
  75. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  76. package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
  77. package/esm/storages/inRedis/index.js +2 -0
  78. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  79. package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
  80. package/esm/storages/pluggable/index.js +2 -0
  81. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +12 -13
  82. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  83. package/esm/sync/polling/pollingManagerCS.js +7 -7
  84. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  85. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  86. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  87. package/esm/sync/polling/updaters/splitChangesUpdater.js +53 -51
  88. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  89. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  90. package/esm/sync/streaming/constants.js +1 -0
  91. package/esm/sync/streaming/pushManager.js +6 -19
  92. package/esm/sync/syncManagerOnline.js +2 -2
  93. package/esm/utils/constants/index.js +2 -1
  94. package/package.json +1 -1
  95. package/src/dtos/types.ts +32 -8
  96. package/src/evaluator/Engine.ts +1 -1
  97. package/src/evaluator/combiners/and.ts +5 -4
  98. package/src/evaluator/combiners/ifelseif.ts +7 -9
  99. package/src/evaluator/condition/engineUtils.ts +1 -1
  100. package/src/evaluator/condition/index.ts +12 -12
  101. package/src/evaluator/index.ts +7 -7
  102. package/src/evaluator/matchers/index.ts +3 -1
  103. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  104. package/src/evaluator/matchers/rbsegment.ts +61 -0
  105. package/src/evaluator/matchersTransform/index.ts +3 -0
  106. package/src/evaluator/parser/index.ts +3 -3
  107. package/src/evaluator/types.ts +2 -2
  108. package/src/evaluator/value/index.ts +2 -2
  109. package/src/evaluator/value/sanitize.ts +5 -4
  110. package/src/logger/constants.ts +2 -3
  111. package/src/logger/messages/debug.ts +3 -4
  112. package/src/logger/messages/warn.ts +1 -1
  113. package/src/sdkManager/index.ts +1 -1
  114. package/src/services/splitApi.ts +2 -2
  115. package/src/services/types.ts +1 -1
  116. package/src/storages/AbstractSplitsCacheAsync.ts +15 -5
  117. package/src/storages/AbstractSplitsCacheSync.ts +14 -15
  118. package/src/storages/KeyBuilder.ts +9 -17
  119. package/src/storages/KeyBuilderCS.ts +15 -4
  120. package/src/storages/KeyBuilderSS.ts +4 -0
  121. package/src/storages/dataLoader.ts +1 -2
  122. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  123. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +15 -16
  124. package/src/storages/inLocalStorage/index.ts +4 -0
  125. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  126. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  127. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  128. package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
  129. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  130. package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
  131. package/src/storages/inRedis/index.ts +2 -0
  132. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  133. package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
  134. package/src/storages/pluggable/index.ts +2 -0
  135. package/src/storages/types.ts +43 -17
  136. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +14 -15
  137. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  138. package/src/sync/polling/fetchers/types.ts +1 -0
  139. package/src/sync/polling/pollingManagerCS.ts +7 -7
  140. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
  141. package/src/sync/polling/types.ts +2 -2
  142. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  143. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  144. package/src/sync/polling/updaters/splitChangesUpdater.ts +64 -62
  145. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  146. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  147. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  148. package/src/sync/streaming/constants.ts +1 -0
  149. package/src/sync/streaming/parseUtils.ts +2 -2
  150. package/src/sync/streaming/pushManager.ts +6 -18
  151. package/src/sync/streaming/types.ts +3 -2
  152. package/src/sync/syncManagerOnline.ts +2 -2
  153. package/src/utils/constants/index.ts +2 -1
  154. package/src/utils/lang/index.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import SplitIO from '../../types/splitio';
2
- import { MaybeThenable, ISplit, IMySegmentsResponse } from '../dtos/types';
2
+ import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types';
3
3
  import { MySegmentsData } from '../sync/polling/types';
4
4
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
5
5
  import { ISettings } from '../types';
@@ -177,11 +177,9 @@ export interface IPluggableStorageWrapper {
177
177
  /** Splits cache */
178
178
 
179
179
  export interface ISplitsCacheBase {
180
- addSplits(entries: [string, ISplit][]): MaybeThenable<boolean[] | void>,
181
- removeSplits(names: string[]): MaybeThenable<boolean[] | void>,
180
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): MaybeThenable<boolean>,
182
181
  getSplit(name: string): MaybeThenable<ISplit | null>,
183
182
  getSplits(names: string[]): MaybeThenable<Record<string, ISplit | null>>, // `fetchMany` in spec
184
- setChangeNumber(changeNumber: number): MaybeThenable<boolean | void>,
185
183
  // should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
186
184
  getChangeNumber(): MaybeThenable<number>,
187
185
  getAll(): MaybeThenable<ISplit[]>,
@@ -198,11 +196,9 @@ export interface ISplitsCacheBase {
198
196
  }
199
197
 
200
198
  export interface ISplitsCacheSync extends ISplitsCacheBase {
201
- addSplits(entries: [string, ISplit][]): boolean[],
202
- removeSplits(names: string[]): boolean[],
199
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean,
203
200
  getSplit(name: string): ISplit | null,
204
201
  getSplits(names: string[]): Record<string, ISplit | null>,
205
- setChangeNumber(changeNumber: number): boolean | void,
206
202
  getChangeNumber(): number,
207
203
  getAll(): ISplit[],
208
204
  getSplitNames(): string[],
@@ -215,11 +211,9 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
215
211
  }
216
212
 
217
213
  export interface ISplitsCacheAsync extends ISplitsCacheBase {
218
- addSplits(entries: [string, ISplit][]): Promise<boolean[] | void>,
219
- removeSplits(names: string[]): Promise<boolean[] | void>,
214
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): Promise<boolean>,
220
215
  getSplit(name: string): Promise<ISplit | null>,
221
216
  getSplits(names: string[]): Promise<Record<string, ISplit | null>>,
222
- setChangeNumber(changeNumber: number): Promise<boolean | void>,
223
217
  getChangeNumber(): Promise<number>,
224
218
  getAll(): Promise<ISplit[]>,
225
219
  getSplitNames(): Promise<string[]>,
@@ -231,6 +225,34 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
231
225
  getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
232
226
  }
233
227
 
228
+ /** Rule-Based Segments cache */
229
+
230
+ export interface IRBSegmentsCacheBase {
231
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): MaybeThenable<boolean>,
232
+ get(name: string): MaybeThenable<IRBSegment | null>,
233
+ getChangeNumber(): MaybeThenable<number>,
234
+ clear(): MaybeThenable<boolean | void>,
235
+ contains(names: Set<string>): MaybeThenable<boolean>,
236
+ }
237
+
238
+ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
239
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean,
240
+ get(name: string): IRBSegment | null,
241
+ getChangeNumber(): number,
242
+ clear(): void,
243
+ contains(names: Set<string>): boolean,
244
+ // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers
245
+ usesSegments(): boolean,
246
+ }
247
+
248
+ export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
249
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean>,
250
+ get(name: string): Promise<IRBSegment | null>,
251
+ getChangeNumber(): Promise<number>,
252
+ clear(): Promise<boolean | void>,
253
+ contains(names: Set<string>): Promise<boolean>,
254
+ }
255
+
234
256
  /** Segments cache */
235
257
 
236
258
  export interface ISegmentsCacheBase {
@@ -428,15 +450,17 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
428
450
  */
429
451
 
430
452
  export interface IStorageBase<
431
- TSplitsCache extends ISplitsCacheBase,
432
- TSegmentsCache extends ISegmentsCacheBase,
433
- TImpressionsCache extends IImpressionsCacheBase,
434
- TImpressionsCountCache extends IImpressionCountsCacheBase,
435
- TEventsCache extends IEventsCacheBase,
436
- TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync,
437
- TUniqueKeysCache extends IUniqueKeysCacheBase
453
+ TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
454
+ TRBSegmentsCache extends IRBSegmentsCacheBase = IRBSegmentsCacheBase,
455
+ TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
456
+ TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
457
+ TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
458
+ TEventsCache extends IEventsCacheBase = IEventsCacheBase,
459
+ TTelemetryCache extends ITelemetryCacheSync | ITelemetryCacheAsync = ITelemetryCacheSync | ITelemetryCacheAsync,
460
+ TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
438
461
  > {
439
462
  splits: TSplitsCache,
463
+ rbSegments: TRBSegmentsCache,
440
464
  segments: TSegmentsCache,
441
465
  impressions: TImpressionsCache,
442
466
  impressionCounts: TImpressionsCountCache,
@@ -449,6 +473,7 @@ export interface IStorageBase<
449
473
 
450
474
  export interface IStorageSync extends IStorageBase<
451
475
  ISplitsCacheSync,
476
+ IRBSegmentsCacheSync,
452
477
  ISegmentsCacheSync,
453
478
  IImpressionsCacheSync,
454
479
  IImpressionCountsCacheSync,
@@ -462,6 +487,7 @@ export interface IStorageSync extends IStorageBase<
462
487
 
463
488
  export interface IStorageAsync extends IStorageBase<
464
489
  ISplitsCacheAsync,
490
+ IRBSegmentsCacheAsync,
465
491
  ISegmentsCacheAsync,
466
492
  IImpressionsCacheAsync | IImpressionsCacheSync,
467
493
  IImpressionCountsCacheBase,
@@ -24,7 +24,7 @@ export function fromObjectUpdaterFactory(
24
24
  let startingUp = true;
25
25
 
26
26
  return function objectUpdater() {
27
- const splits: [string, ISplit][] = [];
27
+ const splits: ISplit[] = [];
28
28
  let loadError = null;
29
29
  let splitsMock: false | Record<string, ISplitPartial> = {};
30
30
  try {
@@ -37,24 +37,23 @@ export function fromObjectUpdaterFactory(
37
37
  if (!loadError && splitsMock) {
38
38
  log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
39
39
 
40
- forOwn(splitsMock, function (val, name) {
41
- splits.push([ // @ts-ignore Split changeNumber and seed is undefined in localhost mode
42
- name, {
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
- ]);
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
+ });
53
52
  });
54
53
 
55
54
  return Promise.all([
56
55
  splitsCache.clear(), // required to sync removed splits from mock
57
- splitsCache.addSplits(splits)
56
+ splitsCache.update(splits, [], Date.now())
58
57
  ]).then(() => {
59
58
  readiness.splits.emit(SDK_SPLITS_ARRIVED);
60
59
 
@@ -11,11 +11,12 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges
11
11
  since: number,
12
12
  noCache?: boolean,
13
13
  till?: number,
14
+ rbSince?: number,
14
15
  // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
15
16
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
16
17
  ) {
17
18
 
18
- let splitsPromise = fetchSplitChanges(since, noCache, till);
19
+ let splitsPromise = fetchSplitChanges(since, noCache, till, rbSince);
19
20
  if (decorator) splitsPromise = decorator(splitsPromise);
20
21
 
21
22
  return splitsPromise.then(resp => resp.json());
@@ -5,6 +5,7 @@ export type ISplitChangesFetcher = (
5
5
  since: number,
6
6
  noCache?: boolean,
7
7
  till?: number,
8
+ rbSince?: number,
8
9
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
9
10
  ) => Promise<ISplitChangesResponse>
10
11
 
@@ -43,10 +43,10 @@ export function pollingManagerCSFactory(
43
43
  // smart pausing
44
44
  readiness.splits.on(SDK_SPLITS_ARRIVED, () => {
45
45
  if (!splitsSyncTask.isRunning()) return; // noop if not doing polling
46
- const splitsHaveSegments = storage.splits.usesSegments();
47
- if (splitsHaveSegments !== mySegmentsSyncTask.isRunning()) {
48
- log.info(POLLING_SMART_PAUSING, [splitsHaveSegments ? 'ON' : 'OFF']);
49
- if (splitsHaveSegments) {
46
+ const usingSegments = storage.splits.usesSegments() || storage.rbSegments.usesSegments();
47
+ if (usingSegments !== mySegmentsSyncTask.isRunning()) {
48
+ log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
49
+ if (usingSegments) {
50
50
  startMySegmentsSyncTasks();
51
51
  } else {
52
52
  stopMySegmentsSyncTasks();
@@ -59,9 +59,9 @@ export function pollingManagerCSFactory(
59
59
 
60
60
  // smart ready
61
61
  function smartReady() {
62
- if (!readiness.isReady() && !storage.splits.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
62
+ if (!readiness.isReady() && !storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
63
63
  }
64
- if (!storage.splits.usesSegments()) setTimeout(smartReady, 0);
64
+ if (!storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) setTimeout(smartReady, 0);
65
65
  else readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
66
66
 
67
67
  mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
@@ -77,7 +77,7 @@ export function pollingManagerCSFactory(
77
77
  log.info(POLLING_START);
78
78
 
79
79
  splitsSyncTask.start();
80
- if (storage.splits.usesSegments()) startMySegmentsSyncTasks();
80
+ if (storage.splits.usesSegments() || storage.rbSegments.usesSegments()) startMySegmentsSyncTasks();
81
81
  },
82
82
 
83
83
  // Stop periodic fetching (polling)
@@ -22,8 +22,7 @@ export function splitsSyncTaskFactory(
22
22
  splitChangesUpdaterFactory(
23
23
  settings.log,
24
24
  splitChangesFetcherFactory(fetchSplitChanges),
25
- storage.splits,
26
- storage.segments,
25
+ storage,
27
26
  settings.sync.__splitFiltersValidation,
28
27
  readiness.splits,
29
28
  settings.startup.requestTimeoutBeforeReady,
@@ -1,10 +1,10 @@
1
- import { ISplit } from '../../dtos/types';
1
+ import { IRBSegment, ISplit } from '../../dtos/types';
2
2
  import { IReadinessManager } from '../../readiness/types';
3
3
  import { IStorageSync } from '../../storages/types';
4
4
  import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../streaming/types';
5
5
  import { ITask, ISyncTask } from '../types';
6
6
 
7
- export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }], boolean> { }
7
+ export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit | IRBSegment, changeNumber: number }], boolean> { }
8
8
 
9
9
  export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
10
10
 
@@ -27,7 +27,7 @@ export function mySegmentsUpdaterFactory(
27
27
  matchingKey: string
28
28
  ): IMySegmentsUpdater {
29
29
 
30
- const { splits, segments, largeSegments } = storage;
30
+ const { splits, rbSegments, segments, largeSegments } = storage;
31
31
  let readyOnAlreadyExistentState = true;
32
32
  let startingUp = true;
33
33
 
@@ -51,7 +51,7 @@ export function mySegmentsUpdaterFactory(
51
51
  }
52
52
 
53
53
  // Notify update if required
54
- if (splits.usesSegments() && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
54
+ if ((splits.usesSegments() || rbSegments.usesSegments()) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
55
55
  readyOnAlreadyExistentState = false;
56
56
  segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
57
57
  }
@@ -51,7 +51,7 @@ export function segmentChangesUpdaterFactory(
51
51
  * Returned promise will not be rejected.
52
52
  *
53
53
  * @param fetchOnlyNew - if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
54
- * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
54
+ * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE or RB_SEGMENT_UPDATE notifications.
55
55
  * @param segmentName - segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
56
56
  * @param noCache - true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
57
57
  * @param till - till target for the provided segmentName, for CDN bypass.
@@ -1,16 +1,18 @@
1
- import { ISegmentsCacheBase, ISplitsCacheBase } from '../../../storages/types';
1
+ import { ISegmentsCacheBase, IStorageBase } from '../../../storages/types';
2
2
  import { ISplitChangesFetcher } from '../fetchers/types';
3
- import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
3
+ import { IRBSegment, ISplit, ISplitChangesResponse, ISplitFiltersValidation, MaybeThenable } from '../../../dtos/types';
4
4
  import { ISplitsEventEmitter } from '../../../readiness/types';
5
5
  import { timeout } from '../../../utils/promise/timeout';
6
6
  import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
7
7
  import { ILogger } from '../../../logger/types';
8
- import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
8
+ import { SYNC_SPLITS_FETCH, SYNC_SPLITS_UPDATE, SYNC_RBS_UPDATE, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
9
9
  import { startsWith } from '../../../utils/lang';
10
- import { IN_SEGMENT } from '../../../utils/constants';
10
+ import { IN_RULE_BASED_SEGMENT, IN_SEGMENT } from '../../../utils/constants';
11
11
  import { setToArray } from '../../../utils/lang/sets';
12
+ import { SPLIT_UPDATE } from '../../streaming/constants';
12
13
 
13
- type ISplitChangesUpdater = (noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }) => Promise<boolean>
14
+ export type InstantUpdate = { payload: ISplit | IRBSegment, changeNumber: number, type: string };
15
+ type SplitChangesUpdater = (noCache?: boolean, till?: number, instantUpdate?: InstantUpdate) => Promise<boolean>
14
16
 
15
17
  // Checks that all registered segments have been fetched (changeNumber !== -1 for every segment).
16
18
  // Returns a promise that could be rejected.
@@ -27,24 +29,24 @@ function checkAllSegmentsExist(segments: ISegmentsCacheBase): Promise<boolean> {
27
29
  * Collect segments from a raw split definition.
28
30
  * Exported for testing purposes.
29
31
  */
30
- export function parseSegments({ conditions }: ISplit): Set<string> {
31
- let segments = new Set<string>();
32
+ export function parseSegments(ruleEntity: ISplit | IRBSegment, matcherType: typeof IN_SEGMENT | typeof IN_RULE_BASED_SEGMENT = IN_SEGMENT): Set<string> {
33
+ const { conditions, excluded } = ruleEntity as IRBSegment;
34
+ const segments = new Set<string>(excluded && excluded.segments);
32
35
 
33
36
  for (let i = 0; i < conditions.length; i++) {
34
37
  const matchers = conditions[i].matcherGroup.matchers;
35
38
 
36
39
  matchers.forEach(matcher => {
37
- if (matcher.matcherType === IN_SEGMENT) segments.add(matcher.userDefinedSegmentMatcherData.segmentName);
40
+ if (matcher.matcherType === matcherType) segments.add(matcher.userDefinedSegmentMatcherData.segmentName);
38
41
  });
39
42
  }
40
43
 
41
44
  return segments;
42
45
  }
43
46
 
44
- interface ISplitMutations {
45
- added: [string, ISplit][],
46
- removed: string[],
47
- segments: string[]
47
+ interface ISplitMutations<T extends ISplit | IRBSegment> {
48
+ added: T[],
49
+ removed: T[]
48
50
  }
49
51
 
50
52
  /**
@@ -73,25 +75,21 @@ function matchFilters(featureFlag: ISplit, filters: ISplitFiltersValidation) {
73
75
  * i.e., an object with added splits, removed splits and used segments.
74
76
  * Exported for testing purposes.
75
77
  */
76
- export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersValidation): ISplitMutations {
77
- const segments = new Set<string>();
78
- const computed = entries.reduce((accum, split) => {
79
- if (split.status === 'ACTIVE' && matchFilters(split, filters)) {
80
- accum.added.push([split.name, split]);
78
+ export function computeMutation<T extends ISplit | IRBSegment>(rules: Array<T>, segments: Set<string>, filters?: ISplitFiltersValidation): ISplitMutations<T> {
81
79
 
82
- parseSegments(split).forEach((segmentName: string) => {
80
+ return rules.reduce((accum, ruleEntity) => {
81
+ if (ruleEntity.status === 'ACTIVE' && (!filters || matchFilters(ruleEntity as ISplit, filters))) {
82
+ accum.added.push(ruleEntity);
83
+
84
+ parseSegments(ruleEntity).forEach((segmentName: string) => {
83
85
  segments.add(segmentName);
84
86
  });
85
87
  } else {
86
- accum.removed.push(split.name);
88
+ accum.removed.push(ruleEntity);
87
89
  }
88
90
 
89
91
  return accum;
90
- }, { added: [], removed: [], segments: [] } as ISplitMutations);
91
-
92
- computed.segments = setToArray(segments);
93
-
94
- return computed;
92
+ }, { added: [], removed: [] } as ISplitMutations<T>);
95
93
  }
96
94
 
97
95
  /**
@@ -111,14 +109,14 @@ export function computeSplitsMutation(entries: ISplit[], filters: ISplitFiltersV
111
109
  export function splitChangesUpdaterFactory(
112
110
  log: ILogger,
113
111
  splitChangesFetcher: ISplitChangesFetcher,
114
- splits: ISplitsCacheBase,
115
- segments: ISegmentsCacheBase,
112
+ storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments'>,
116
113
  splitFiltersValidation: ISplitFiltersValidation,
117
114
  splitsEventEmitter?: ISplitsEventEmitter,
118
115
  requestTimeoutBeforeReady: number = 0,
119
116
  retriesOnFailureBeforeReady: number = 0,
120
117
  isClientSide?: boolean
121
- ): ISplitChangesUpdater {
118
+ ): SplitChangesUpdater {
119
+ const { splits, rbSegments, segments } = storage;
122
120
 
123
121
  let startingUp = true;
124
122
 
@@ -128,16 +126,6 @@ export function splitChangesUpdaterFactory(
128
126
  return promise;
129
127
  }
130
128
 
131
- /** Returns true if at least one split was updated */
132
- function isThereUpdate(flagsChange: [boolean | void, void | boolean[], void | boolean[], boolean | void] | [any, any, any]) {
133
- const [, added, removed] = flagsChange;
134
- // There is at least one added or modified feature flag
135
- if (added && added.some((update: boolean) => update)) return true;
136
- // There is at least one removed feature flag
137
- if (removed && removed.some((update: boolean) => update)) return true;
138
- return false;
139
- }
140
-
141
129
  /**
142
130
  * SplitChanges updater returns a promise that resolves with a `false` boolean value if it fails to fetch splits or synchronize them with the storage.
143
131
  * Returned promise will not be rejected.
@@ -145,39 +133,53 @@ export function splitChangesUpdaterFactory(
145
133
  * @param noCache - true to revalidate data to fetch
146
134
  * @param till - query param to bypass CDN requests
147
135
  */
148
- return function splitChangesUpdater(noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }) {
136
+ return function splitChangesUpdater(noCache?: boolean, till?: number, instantUpdate?: InstantUpdate) {
149
137
 
150
138
  /**
151
139
  * @param since - current changeNumber at splitsCache
152
140
  * @param retry - current number of retry attempts
153
141
  */
154
- function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
155
- log.debug(SYNC_SPLITS_FETCH, [since]);
156
- const fetcherPromise = Promise.resolve(splitUpdateNotification ?
157
- { splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
158
- splitChangesFetcher(since, noCache, till, _promiseDecorator)
142
+ function _splitChangesUpdater(sinces: [number, number], retry = 0): Promise<boolean> {
143
+ const [since, rbSince] = sinces;
144
+ log.debug(SYNC_SPLITS_FETCH, sinces);
145
+ const fetcherPromise = Promise.resolve(
146
+ instantUpdate ?
147
+ instantUpdate.type === SPLIT_UPDATE ?
148
+ // IFFU edge case: a change to a flag that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
149
+ Promise.resolve(rbSegments.contains(parseSegments(instantUpdate.payload, IN_RULE_BASED_SEGMENT))).then((contains) => {
150
+ return contains ?
151
+ { ff: { d: [instantUpdate.payload as ISplit], t: instantUpdate.changeNumber } } :
152
+ splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
153
+ }) :
154
+ { rbs: { d: [instantUpdate.payload as IRBSegment], t: instantUpdate.changeNumber } } :
155
+ splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator)
159
156
  )
160
157
  .then((splitChanges: ISplitChangesResponse) => {
161
158
  startingUp = false;
162
159
 
163
- const mutation = computeSplitsMutation(splitChanges.splits, splitFiltersValidation);
164
-
165
- log.debug(SYNC_SPLITS_NEW, [mutation.added.length]);
166
- log.debug(SYNC_SPLITS_REMOVED, [mutation.removed.length]);
167
- log.debug(SYNC_SPLITS_SEGMENTS, [mutation.segments.length]);
168
-
169
- // Write into storage
170
- // @TODO call `setChangeNumber` only if the other storage operations have succeeded, in order to keep storage consistency
171
- return Promise.all([
172
- // calling first `setChangenumber` method, to perform cache flush if split filter queryString changed
173
- splits.setChangeNumber(splitChanges.till),
174
- splits.addSplits(mutation.added),
175
- splits.removeSplits(mutation.removed),
176
- segments.registerSegments(mutation.segments)
177
- ]).then((flagsChange) => {
160
+ const usedSegments = new Set<string>();
161
+
162
+ let ffUpdate: MaybeThenable<boolean> = false;
163
+ if (splitChanges.ff) {
164
+ const { added, removed } = computeMutation(splitChanges.ff.d, usedSegments, splitFiltersValidation);
165
+ log.debug(SYNC_SPLITS_UPDATE, [added.length, removed.length]);
166
+ ffUpdate = splits.update(added, removed, splitChanges.ff.t);
167
+ }
168
+
169
+ let rbsUpdate: MaybeThenable<boolean> = false;
170
+ if (splitChanges.rbs) {
171
+ const { added, removed } = computeMutation(splitChanges.rbs.d, usedSegments);
172
+ log.debug(SYNC_RBS_UPDATE, [added.length, removed.length]);
173
+ rbsUpdate = rbSegments.update(added, removed, splitChanges.rbs.t);
174
+ }
175
+
176
+ return Promise.all([ffUpdate, rbsUpdate,
177
+ // @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
178
+ segments.registerSegments(setToArray(usedSegments))
179
+ ]).then(([ffChanged, rbsChanged]) => {
178
180
  if (splitsEventEmitter) {
179
181
  // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
180
- return Promise.resolve(!splitsEventEmitter.splitsArrived || (since !== splitChanges.till && isThereUpdate(flagsChange) && (isClientSide || checkAllSegmentsExist(segments))))
182
+ return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
181
183
  .catch(() => false /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
182
184
  .then(emitSplitsArrivedEvent => {
183
185
  // emit SDK events
@@ -194,7 +196,7 @@ export function splitChangesUpdaterFactory(
194
196
  if (startingUp && retriesOnFailureBeforeReady > retry) {
195
197
  retry += 1;
196
198
  log.info(SYNC_SPLITS_FETCH_RETRY, [retry, error]);
197
- return _splitChangesUpdater(since, retry);
199
+ return _splitChangesUpdater(sinces, retry);
198
200
  } else {
199
201
  startingUp = false;
200
202
  }
@@ -211,7 +213,7 @@ export function splitChangesUpdaterFactory(
211
213
  return fetcherPromise;
212
214
  }
213
215
 
214
- let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
215
- return sincePromise.then(_splitChangesUpdater);
216
+ // `getChangeNumber` never rejects or throws error
217
+ return Promise.all([splits.getChangeNumber(), rbSegments.getChangeNumber()]).then(_splitChangesUpdater);
216
218
  };
217
219
  }
@@ -1,6 +1,6 @@
1
1
  import { errorParser, messageParser } from './NotificationParser';
2
2
  import { notificationKeeperFactory } from './NotificationKeeper';
3
- import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE } from '../constants';
3
+ import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, RB_SEGMENT_UPDATE } from '../constants';
4
4
  import { IPushEventEmitter } from '../types';
5
5
  import { ISseEventHandler } from '../SSEClient/types';
6
6
  import { INotificationError, INotificationMessage } from './types';
@@ -84,6 +84,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
84
84
  case MEMBERSHIPS_MS_UPDATE:
85
85
  case MEMBERSHIPS_LS_UPDATE:
86
86
  case SPLIT_KILL:
87
+ case RB_SEGMENT_UPDATE:
87
88
  pushEmitter.emit(parsedData.type, parsedData);
88
89
  break;
89
90
 
@@ -1,5 +1,5 @@
1
1
  import { ControlType } from '../constants';
2
- import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../types';
2
+ import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE, RB_SEGMENT_UPDATE } from '../types';
3
3
 
4
4
  export enum Compression {
5
5
  None = 0,
@@ -42,7 +42,7 @@ export interface ISegmentUpdateData {
42
42
  }
43
43
 
44
44
  export interface ISplitUpdateData {
45
- type: SPLIT_UPDATE,
45
+ type: SPLIT_UPDATE | RB_SEGMENT_UPDATE,
46
46
  changeNumber: number,
47
47
  pcn?: number,
48
48
  d?: string,