@splitsoftware/splitio-commons 2.1.0-rc.2 → 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 (174) hide show
  1. package/CHANGES.txt +2 -7
  2. package/README.md +1 -0
  3. package/cjs/evaluator/combiners/and.js +2 -6
  4. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  5. package/cjs/evaluator/condition/index.js +6 -5
  6. package/cjs/evaluator/index.js +7 -7
  7. package/cjs/evaluator/matchers/index.js +3 -1
  8. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  9. package/cjs/evaluator/matchers/rbsegment.js +43 -0
  10. package/cjs/evaluator/matchersTransform/index.js +4 -0
  11. package/cjs/evaluator/parser/index.js +2 -2
  12. package/cjs/evaluator/value/sanitize.js +1 -0
  13. package/cjs/logger/constants.js +5 -6
  14. package/cjs/logger/messages/debug.js +3 -4
  15. package/cjs/logger/messages/warn.js +1 -1
  16. package/cjs/readiness/readinessManager.js +0 -6
  17. package/cjs/services/splitApi.js +2 -2
  18. package/cjs/storages/AbstractSplitsCacheAsync.js +19 -1
  19. package/cjs/storages/AbstractSplitsCacheSync.js +17 -9
  20. package/cjs/storages/KeyBuilder.js +8 -15
  21. package/cjs/storages/KeyBuilderCS.js +11 -5
  22. package/cjs/storages/KeyBuilderSS.js +3 -0
  23. package/cjs/storages/dataLoader.js +3 -5
  24. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  25. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  26. package/cjs/storages/inLocalStorage/index.js +7 -5
  27. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  28. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  29. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  30. package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
  31. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  32. package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
  33. package/cjs/storages/inRedis/constants.js +1 -1
  34. package/cjs/storages/inRedis/index.js +2 -0
  35. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  36. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
  37. package/cjs/storages/pluggable/index.js +3 -2
  38. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  39. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  40. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  41. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  42. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  43. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  44. package/cjs/sync/polling/updaters/splitChangesUpdater.js +62 -51
  45. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  46. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  47. package/cjs/sync/streaming/constants.js +2 -1
  48. package/cjs/sync/streaming/pushManager.js +3 -16
  49. package/cjs/sync/syncManagerOnline.js +5 -10
  50. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  51. package/cjs/utils/constants/browser.js +5 -0
  52. package/cjs/utils/constants/index.js +3 -2
  53. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  54. package/esm/evaluator/combiners/and.js +2 -6
  55. package/esm/evaluator/combiners/ifelseif.js +7 -7
  56. package/esm/evaluator/condition/index.js +6 -5
  57. package/esm/evaluator/index.js +7 -7
  58. package/esm/evaluator/matchers/index.js +3 -1
  59. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  60. package/esm/evaluator/matchers/rbsegment.js +39 -0
  61. package/esm/evaluator/matchersTransform/index.js +4 -0
  62. package/esm/evaluator/parser/index.js +2 -2
  63. package/esm/evaluator/value/sanitize.js +1 -0
  64. package/esm/logger/constants.js +2 -3
  65. package/esm/logger/messages/debug.js +3 -4
  66. package/esm/logger/messages/warn.js +1 -1
  67. package/esm/readiness/readinessManager.js +0 -6
  68. package/esm/services/splitApi.js +2 -2
  69. package/esm/storages/AbstractSplitsCacheAsync.js +19 -1
  70. package/esm/storages/AbstractSplitsCacheSync.js +17 -9
  71. package/esm/storages/KeyBuilder.js +8 -15
  72. package/esm/storages/KeyBuilderCS.js +11 -5
  73. package/esm/storages/KeyBuilderSS.js +3 -0
  74. package/esm/storages/dataLoader.js +2 -4
  75. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  76. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  77. package/esm/storages/inLocalStorage/index.js +7 -5
  78. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  79. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  80. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  81. package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
  82. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  83. package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
  84. package/esm/storages/inRedis/constants.js +1 -1
  85. package/esm/storages/inRedis/index.js +2 -0
  86. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  87. package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
  88. package/esm/storages/pluggable/index.js +3 -2
  89. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  90. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  91. package/esm/sync/polling/pollingManagerCS.js +7 -7
  92. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  93. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  94. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  95. package/esm/sync/polling/updaters/splitChangesUpdater.js +63 -52
  96. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  97. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  98. package/esm/sync/streaming/constants.js +1 -0
  99. package/esm/sync/streaming/pushManager.js +6 -19
  100. package/esm/sync/syncManagerOnline.js +5 -10
  101. package/esm/trackers/uniqueKeysTracker.js +1 -1
  102. package/esm/utils/constants/browser.js +2 -0
  103. package/esm/utils/constants/index.js +2 -1
  104. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  105. package/package.json +1 -1
  106. package/src/dtos/types.ts +32 -8
  107. package/src/evaluator/Engine.ts +1 -1
  108. package/src/evaluator/combiners/and.ts +5 -4
  109. package/src/evaluator/combiners/ifelseif.ts +7 -9
  110. package/src/evaluator/condition/engineUtils.ts +1 -1
  111. package/src/evaluator/condition/index.ts +12 -12
  112. package/src/evaluator/index.ts +7 -7
  113. package/src/evaluator/matchers/index.ts +3 -1
  114. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  115. package/src/evaluator/matchers/rbsegment.ts +61 -0
  116. package/src/evaluator/matchersTransform/index.ts +3 -0
  117. package/src/evaluator/parser/index.ts +3 -3
  118. package/src/evaluator/types.ts +2 -2
  119. package/src/evaluator/value/index.ts +2 -2
  120. package/src/evaluator/value/sanitize.ts +5 -4
  121. package/src/logger/constants.ts +2 -3
  122. package/src/logger/messages/debug.ts +3 -4
  123. package/src/logger/messages/warn.ts +1 -1
  124. package/src/readiness/readinessManager.ts +0 -5
  125. package/src/sdkManager/index.ts +1 -1
  126. package/src/services/splitApi.ts +2 -2
  127. package/src/services/types.ts +1 -1
  128. package/src/storages/AbstractSplitsCacheAsync.ts +23 -5
  129. package/src/storages/AbstractSplitsCacheSync.ts +22 -15
  130. package/src/storages/KeyBuilder.ts +9 -17
  131. package/src/storages/KeyBuilderCS.ts +13 -6
  132. package/src/storages/KeyBuilderSS.ts +4 -0
  133. package/src/storages/dataLoader.ts +2 -5
  134. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  135. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +80 -16
  136. package/src/storages/inLocalStorage/index.ts +12 -8
  137. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  138. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  139. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  140. package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
  141. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  142. package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
  143. package/src/storages/inRedis/constants.ts +1 -1
  144. package/src/storages/inRedis/index.ts +2 -0
  145. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  146. package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
  147. package/src/storages/pluggable/index.ts +3 -2
  148. package/src/storages/types.ts +47 -18
  149. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +19 -21
  150. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  151. package/src/sync/polling/fetchers/types.ts +1 -0
  152. package/src/sync/polling/pollingManagerCS.ts +7 -7
  153. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
  154. package/src/sync/polling/types.ts +2 -2
  155. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  156. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  157. package/src/sync/polling/updaters/splitChangesUpdater.ts +74 -63
  158. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  159. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  160. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  161. package/src/sync/streaming/constants.ts +1 -0
  162. package/src/sync/streaming/parseUtils.ts +2 -2
  163. package/src/sync/streaming/pushManager.ts +6 -18
  164. package/src/sync/streaming/types.ts +3 -2
  165. package/src/sync/syncManagerOnline.ts +5 -11
  166. package/src/trackers/uniqueKeysTracker.ts +1 -1
  167. package/src/utils/constants/browser.ts +2 -0
  168. package/src/utils/constants/index.ts +2 -1
  169. package/src/utils/lang/index.ts +2 -2
  170. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  171. package/types/splitio.d.ts +1 -25
  172. package/cjs/storages/inLocalStorage/validateCache.js +0 -79
  173. package/esm/storages/inLocalStorage/validateCache.js +0 -75
  174. package/src/storages/inLocalStorage/validateCache.ts +0 -91
@@ -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
- import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
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
- return 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,15 +196,24 @@ 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
  }
201
203
  return false;
202
204
  });
205
+
206
+ // After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
207
+ // Wrapping in a promise since checkCache can be async.
208
+ if (splitsEventEmitter && startingUp) {
209
+ Promise.resolve(splits.checkCache()).then(isCacheReady => {
210
+ if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
211
+ });
212
+ }
213
+ return fetcherPromise;
203
214
  }
204
215
 
205
- let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
206
- return sincePromise.then(_splitChangesUpdater);
216
+ // `getChangeNumber` never rejects or throws error
217
+ return Promise.all([splits.getChangeNumber(), rbSegments.getChangeNumber()]).then(_splitChangesUpdater);
207
218
  };
208
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,
@@ -1,12 +1,16 @@
1
- import { ISplit } from '../../../dtos/types';
1
+ import { IRBSegment, ISplit } from '../../../dtos/types';
2
+ import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
2
3
  import { ILogger } from '../../../logger/types';
3
4
  import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
4
5
  import { ISplitsEventEmitter } from '../../../readiness/types';
5
- import { ISplitsCacheSync } from '../../../storages/types';
6
+ import { IRBSegmentsCacheSync, ISplitsCacheSync, IStorageSync } from '../../../storages/types';
6
7
  import { ITelemetryTracker } from '../../../trackers/types';
7
8
  import { Backoff } from '../../../utils/Backoff';
8
9
  import { SPLITS } from '../../../utils/constants';
9
10
  import { ISegmentsSyncTask, ISplitsSyncTask } from '../../polling/types';
11
+ import { InstantUpdate } from '../../polling/updaters/splitChangesUpdater';
12
+ import { RB_SEGMENT_UPDATE } from '../constants';
13
+ import { parseFFUpdatePayload } from '../parseUtils';
10
14
  import { ISplitKillData, ISplitUpdateData } from '../SSEHandler/types';
11
15
  import { FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT, FETCH_BACKOFF_MAX_RETRIES } from './constants';
12
16
  import { IUpdateWorker } from './types';
@@ -14,102 +18,128 @@ import { IUpdateWorker } from './types';
14
18
  /**
15
19
  * SplitsUpdateWorker factory
16
20
  */
17
- export function SplitsUpdateWorker(log: ILogger, splitsCache: ISplitsCacheSync, splitsSyncTask: ISplitsSyncTask, splitsEventEmitter: ISplitsEventEmitter, telemetryTracker: ITelemetryTracker, segmentsSyncTask?: ISegmentsSyncTask): IUpdateWorker<[updateData: ISplitUpdateData, payload?: ISplit]> & { killSplit(event: ISplitKillData): void } {
21
+ export function SplitsUpdateWorker(log: ILogger, storage: IStorageSync, splitsSyncTask: ISplitsSyncTask, splitsEventEmitter: ISplitsEventEmitter, telemetryTracker: ITelemetryTracker, segmentsSyncTask?: ISegmentsSyncTask): IUpdateWorker<[updateData: ISplitUpdateData]> & { killSplit(event: ISplitKillData): void } {
18
22
 
19
- let maxChangeNumber = 0;
20
- let handleNewEvent = false;
21
- let isHandlingEvent: boolean;
22
- let cdnBypass: boolean;
23
- let payload: ISplit | undefined;
24
- const backoff = new Backoff(__handleSplitUpdateCall, FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT);
23
+ const ff = SplitsUpdateWorker(storage.splits);
24
+ const rbs = SplitsUpdateWorker(storage.rbSegments);
25
25
 
26
- function __handleSplitUpdateCall() {
27
- isHandlingEvent = true;
28
- if (maxChangeNumber > splitsCache.getChangeNumber()) {
29
- handleNewEvent = false;
30
- const splitUpdateNotification = payload ? { payload, changeNumber: maxChangeNumber } : undefined;
31
- // fetch splits revalidating data if cached
32
- splitsSyncTask.execute(true, cdnBypass ? maxChangeNumber : undefined, splitUpdateNotification).then(() => {
33
- if (!isHandlingEvent) return; // halt if `stop` has been called
34
- if (handleNewEvent) {
35
- __handleSplitUpdateCall();
36
- } else {
37
- if (splitUpdateNotification) telemetryTracker.trackUpdatesFromSSE(SPLITS);
38
- // fetch new registered segments for server-side API. Not retrying on error
39
- if (segmentsSyncTask) segmentsSyncTask.execute(true);
26
+ function SplitsUpdateWorker(cache: ISplitsCacheSync | IRBSegmentsCacheSync) {
27
+ let maxChangeNumber = -1;
28
+ let handleNewEvent = false;
29
+ let isHandlingEvent: boolean;
30
+ let cdnBypass: boolean;
31
+ let instantUpdate: InstantUpdate | undefined;
32
+ const backoff = new Backoff(__handleSplitUpdateCall, FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT);
40
33
 
41
- const attempts = backoff.attempts + 1;
34
+ function __handleSplitUpdateCall() {
35
+ isHandlingEvent = true;
36
+ if (maxChangeNumber > cache.getChangeNumber()) {
37
+ handleNewEvent = false;
38
+ // fetch splits revalidating data if cached
39
+ splitsSyncTask.execute(true, cdnBypass ? maxChangeNumber : undefined, instantUpdate).then(() => {
40
+ if (!isHandlingEvent) return; // halt if `stop` has been called
41
+ if (handleNewEvent) {
42
+ __handleSplitUpdateCall();
43
+ } else {
44
+ if (instantUpdate) telemetryTracker.trackUpdatesFromSSE(SPLITS);
45
+ // fetch new registered segments for server-side API. Not retrying on error
46
+ if (segmentsSyncTask) segmentsSyncTask.execute(true);
42
47
 
43
- if (maxChangeNumber <= splitsCache.getChangeNumber()) {
44
- log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`);
45
- isHandlingEvent = false;
46
- return;
47
- }
48
+ const attempts = backoff.attempts + 1;
48
49
 
49
- if (attempts < FETCH_BACKOFF_MAX_RETRIES) {
50
- backoff.scheduleCall();
51
- return;
52
- }
50
+ if (ff.isSync() && rbs.isSync()) {
51
+ log.debug(`Refresh completed${cdnBypass ? ' bypassing the CDN' : ''} in ${attempts} attempts.`);
52
+ isHandlingEvent = false;
53
+ return;
54
+ }
53
55
 
54
- if (cdnBypass) {
55
- log.debug(`No changes fetched after ${attempts} attempts with CDN bypassed.`);
56
- isHandlingEvent = false;
57
- } else {
58
- backoff.reset();
59
- cdnBypass = true;
60
- __handleSplitUpdateCall();
56
+ if (attempts < FETCH_BACKOFF_MAX_RETRIES) {
57
+ backoff.scheduleCall();
58
+ return;
59
+ }
60
+
61
+ if (cdnBypass) {
62
+ log.debug(`No changes fetched after ${attempts} attempts with CDN bypassed.`);
63
+ isHandlingEvent = false;
64
+ } else {
65
+ backoff.reset();
66
+ cdnBypass = true;
67
+ __handleSplitUpdateCall();
68
+ }
61
69
  }
62
- }
63
- });
64
- } else {
65
- isHandlingEvent = false;
70
+ });
71
+ } else {
72
+ isHandlingEvent = false;
73
+ }
66
74
  }
67
- }
68
75
 
69
- /**
70
- * Invoked by NotificationProcessor on SPLIT_UPDATE event
71
- *
72
- * @param changeNumber - change number of the SPLIT_UPDATE notification
73
- */
74
- function put({ changeNumber, pcn }: ISplitUpdateData, _payload?: ISplit) {
75
- const currentChangeNumber = splitsCache.getChangeNumber();
76
+ return {
77
+ /**
78
+ * Invoked by NotificationProcessor on SPLIT_UPDATE or RB_SEGMENT_UPDATE event
79
+ *
80
+ * @param changeNumber - change number of the notification
81
+ */
82
+ put({ changeNumber, pcn, type }: ISplitUpdateData, payload?: ISplit | IRBSegment) {
83
+ const currentChangeNumber = cache.getChangeNumber();
76
84
 
77
- if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
85
+ if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
78
86
 
79
- maxChangeNumber = changeNumber;
80
- handleNewEvent = true;
81
- cdnBypass = false;
82
- payload = undefined;
87
+ maxChangeNumber = changeNumber;
88
+ handleNewEvent = true;
89
+ cdnBypass = false;
90
+ instantUpdate = undefined;
83
91
 
84
- if (_payload && currentChangeNumber === pcn) {
85
- payload = _payload;
86
- }
92
+ if (payload && currentChangeNumber === pcn) {
93
+ instantUpdate = { payload, changeNumber, type };
94
+ }
87
95
 
88
- if (backoff.timeoutID || !isHandlingEvent) __handleSplitUpdateCall();
89
- backoff.reset();
96
+ if (backoff.timeoutID || !isHandlingEvent) __handleSplitUpdateCall();
97
+ backoff.reset();
98
+ },
99
+ stop() {
100
+ isHandlingEvent = false;
101
+ backoff.reset();
102
+ },
103
+ isSync() {
104
+ return maxChangeNumber <= cache.getChangeNumber();
105
+ }
106
+ };
90
107
  }
91
108
 
92
109
  return {
93
- put,
110
+ put(parsedData) {
111
+ if (parsedData.d && parsedData.c !== undefined) {
112
+ try {
113
+ const payload = parseFFUpdatePayload(parsedData.c, parsedData.d);
114
+ if (payload) {
115
+ (parsedData.type === RB_SEGMENT_UPDATE ? rbs : ff).put(parsedData, payload);
116
+ return;
117
+ }
118
+ } catch (e) {
119
+ log.warn(STREAMING_PARSING_SPLIT_UPDATE, [parsedData.type, e]);
120
+ }
121
+ }
122
+ (parsedData.type === RB_SEGMENT_UPDATE ? rbs : ff).put(parsedData);
123
+ },
94
124
  /**
95
125
  * Invoked by NotificationProcessor on SPLIT_KILL event
96
126
  *
97
- * @param changeNumber - change number of the SPLIT_UPDATE notification
127
+ * @param changeNumber - change number of the notification
98
128
  * @param splitName - name of split to kill
99
129
  * @param defaultTreatment - default treatment value
100
130
  */
101
131
  killSplit({ changeNumber, splitName, defaultTreatment }: ISplitKillData) {
102
- if (splitsCache.killLocally(splitName, defaultTreatment, changeNumber)) {
132
+ if (storage.splits.killLocally(splitName, defaultTreatment, changeNumber)) {
103
133
  // trigger an SDK_UPDATE if Split was killed locally
104
134
  splitsEventEmitter.emit(SDK_SPLITS_ARRIVED, true);
105
135
  }
106
136
  // queues the SplitChanges fetch (only if changeNumber is newer)
107
- put({ changeNumber } as ISplitUpdateData);
137
+ ff.put({ changeNumber } as ISplitUpdateData);
108
138
  },
109
139
 
110
140
  stop() {
111
- isHandlingEvent = false;
112
- backoff.reset();
141
+ ff.stop();
142
+ rbs.stop();
113
143
  }
114
144
  };
115
145
  }
@@ -30,6 +30,7 @@ export const MEMBERSHIPS_LS_UPDATE = 'MEMBERSHIPS_LS_UPDATE';
30
30
  export const SEGMENT_UPDATE = 'SEGMENT_UPDATE';
31
31
  export const SPLIT_KILL = 'SPLIT_KILL';
32
32
  export const SPLIT_UPDATE = 'SPLIT_UPDATE';
33
+ export const RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE';
33
34
 
34
35
  // Control-type push notifications, handled by NotificationKeeper
35
36
  export const CONTROL = 'CONTROL';
@@ -2,7 +2,7 @@ import { algorithms } from '../../utils/decompress';
2
2
  import { decodeFromBase64 } from '../../utils/base64';
3
3
  import { hash } from '../../utils/murmur3/murmur3';
4
4
  import { Compression, IMembershipMSUpdateData, KeyList } from './SSEHandler/types';
5
- import { ISplit } from '../../dtos/types';
5
+ import { IRBSegment, ISplit } from '../../dtos/types';
6
6
 
7
7
  const GZIP = 1;
8
8
  const ZLIB = 2;
@@ -82,7 +82,7 @@ export function isInBitmap(bitmap: Uint8Array, hash64hex: string) {
82
82
  /**
83
83
  * Parse feature flags notifications for instant feature flag updates
84
84
  */
85
- export function parseFFUpdatePayload(compression: Compression, data: string): ISplit | undefined {
85
+ export function parseFFUpdatePayload(compression: Compression, data: string): ISplit | IRBSegment | undefined {
86
86
  return compression > 0 ?
87
87
  parseKeyList(data, compression, false) :
88
88
  JSON.parse(decodeFromBase64(data));