@splitsoftware/splitio-commons 1.16.0 → 1.16.1-rc.1

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 (148) hide show
  1. package/CHANGES.txt +3 -0
  2. package/cjs/evaluator/matchers/index.js +3 -1
  3. package/cjs/evaluator/matchers/large_segment.js +16 -0
  4. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  5. package/cjs/evaluator/matchersTransform/index.js +1 -1
  6. package/cjs/logger/constants.js +4 -4
  7. package/cjs/logger/messages/info.js +0 -1
  8. package/cjs/readiness/readinessManager.js +14 -10
  9. package/cjs/readiness/sdkReadinessManager.js +5 -6
  10. package/cjs/sdkClient/sdkClientMethodCS.js +3 -4
  11. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +4 -5
  12. package/cjs/sdkFactory/index.js +1 -1
  13. package/cjs/services/splitApi.js +4 -0
  14. package/cjs/storages/AbstractSplitsCacheAsync.js +2 -2
  15. package/cjs/storages/AbstractSplitsCacheSync.js +5 -5
  16. package/cjs/storages/KeyBuilder.js +3 -0
  17. package/cjs/storages/KeyBuilderCS.js +17 -5
  18. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +16 -4
  19. package/cjs/storages/inLocalStorage/index.js +6 -2
  20. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -0
  21. package/cjs/storages/inMemory/SplitsCacheInMemory.js +20 -11
  22. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +7 -10
  23. package/cjs/storages/pluggable/inMemoryWrapper.js +1 -1
  24. package/cjs/sync/polling/fetchers/mySegmentsFetcher.js +5 -1
  25. package/cjs/sync/polling/pollingManagerCS.js +51 -33
  26. package/cjs/sync/polling/syncTasks/mySegmentsSyncTask.js +2 -2
  27. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +5 -6
  28. package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -1
  29. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  30. package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +15 -5
  31. package/cjs/sync/streaming/constants.js +2 -1
  32. package/cjs/sync/streaming/pushManager.js +95 -64
  33. package/cjs/sync/submitters/telemetrySubmitter.js +2 -0
  34. package/cjs/sync/syncManagerOnline.js +24 -14
  35. package/cjs/utils/constants/index.js +5 -1
  36. package/cjs/utils/settingsValidation/index.js +9 -4
  37. package/esm/evaluator/matchers/index.js +3 -1
  38. package/esm/evaluator/matchers/large_segment.js +12 -0
  39. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  40. package/esm/evaluator/matchersTransform/index.js +1 -1
  41. package/esm/logger/constants.js +1 -1
  42. package/esm/logger/messages/info.js +0 -1
  43. package/esm/readiness/readinessManager.js +14 -10
  44. package/esm/readiness/sdkReadinessManager.js +5 -6
  45. package/esm/sdkClient/sdkClientMethodCS.js +4 -5
  46. package/esm/sdkClient/sdkClientMethodCSWithTT.js +5 -6
  47. package/esm/sdkFactory/index.js +1 -1
  48. package/esm/services/splitApi.js +5 -1
  49. package/esm/storages/AbstractSplitsCacheAsync.js +2 -2
  50. package/esm/storages/AbstractSplitsCacheSync.js +3 -3
  51. package/esm/storages/KeyBuilder.js +3 -0
  52. package/esm/storages/KeyBuilderCS.js +15 -4
  53. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +17 -5
  54. package/esm/storages/inLocalStorage/index.js +7 -3
  55. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -0
  56. package/esm/storages/inMemory/SplitsCacheInMemory.js +21 -12
  57. package/esm/storages/inMemory/TelemetryCacheInMemory.js +7 -10
  58. package/esm/storages/pluggable/inMemoryWrapper.js +1 -1
  59. package/esm/sync/polling/fetchers/mySegmentsFetcher.js +5 -1
  60. package/esm/sync/polling/pollingManagerCS.js +52 -34
  61. package/esm/sync/polling/syncTasks/mySegmentsSyncTask.js +2 -2
  62. package/esm/sync/polling/updaters/mySegmentsUpdater.js +3 -4
  63. package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -1
  64. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  65. package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +15 -5
  66. package/esm/sync/streaming/constants.js +1 -0
  67. package/esm/sync/streaming/pushManager.js +95 -65
  68. package/esm/sync/submitters/telemetrySubmitter.js +2 -0
  69. package/esm/sync/syncManagerOnline.js +25 -15
  70. package/esm/utils/constants/index.js +4 -0
  71. package/esm/utils/settingsValidation/index.js +10 -5
  72. package/package.json +1 -1
  73. package/src/dtos/types.ts +17 -7
  74. package/src/evaluator/matchers/index.ts +2 -0
  75. package/src/evaluator/matchers/large_segment.ts +18 -0
  76. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  77. package/src/evaluator/matchersTransform/index.ts +1 -1
  78. package/src/logger/constants.ts +1 -1
  79. package/src/logger/messages/info.ts +0 -1
  80. package/src/readiness/readinessManager.ts +13 -9
  81. package/src/readiness/sdkReadinessManager.ts +7 -7
  82. package/src/readiness/types.ts +3 -2
  83. package/src/sdkClient/sdkClientMethodCS.ts +4 -6
  84. package/src/sdkClient/sdkClientMethodCSWithTT.ts +5 -7
  85. package/src/sdkFactory/index.ts +1 -1
  86. package/src/services/splitApi.ts +6 -1
  87. package/src/services/types.ts +1 -0
  88. package/src/storages/AbstractSplitsCacheAsync.ts +2 -2
  89. package/src/storages/AbstractSplitsCacheSync.ts +4 -4
  90. package/src/storages/KeyBuilder.ts +3 -0
  91. package/src/storages/KeyBuilderCS.ts +25 -5
  92. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +3 -3
  93. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +20 -5
  94. package/src/storages/inLocalStorage/index.ts +8 -4
  95. package/src/storages/inMemory/InMemoryStorageCS.ts +5 -0
  96. package/src/storages/inMemory/SplitsCacheInMemory.ts +15 -10
  97. package/src/storages/inMemory/TelemetryCacheInMemory.ts +7 -11
  98. package/src/storages/pluggable/inMemoryWrapper.ts +1 -1
  99. package/src/storages/types.ts +7 -5
  100. package/src/sync/polling/fetchers/mySegmentsFetcher.ts +6 -2
  101. package/src/sync/polling/pollingManagerCS.ts +61 -29
  102. package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +10 -10
  103. package/src/sync/polling/types.ts +3 -2
  104. package/src/sync/polling/updaters/mySegmentsUpdater.ts +5 -8
  105. package/src/sync/polling/updaters/splitChangesUpdater.ts +4 -3
  106. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  107. package/src/sync/streaming/SSEHandler/types.ts +14 -2
  108. package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +17 -5
  109. package/src/sync/streaming/constants.ts +1 -0
  110. package/src/sync/streaming/pushManager.ts +100 -63
  111. package/src/sync/streaming/types.ts +5 -3
  112. package/src/sync/submitters/telemetrySubmitter.ts +2 -0
  113. package/src/sync/submitters/types.ts +10 -4
  114. package/src/sync/syncManagerOnline.ts +19 -11
  115. package/src/types.ts +26 -1
  116. package/src/utils/constants/index.ts +5 -0
  117. package/src/utils/settingsValidation/index.ts +11 -6
  118. package/src/utils/settingsValidation/types.ts +1 -1
  119. package/types/dtos/types.d.ts +14 -6
  120. package/types/evaluator/matchers/large_segment.d.ts +5 -0
  121. package/types/logger/constants.d.ts +1 -1
  122. package/types/readiness/readinessManager.d.ts +2 -2
  123. package/types/readiness/sdkReadinessManager.d.ts +2 -3
  124. package/types/readiness/types.d.ts +3 -2
  125. package/types/services/types.d.ts +1 -0
  126. package/types/storages/AbstractSplitsCacheAsync.d.ts +1 -1
  127. package/types/storages/AbstractSplitsCacheSync.d.ts +3 -3
  128. package/types/storages/KeyBuilder.d.ts +1 -0
  129. package/types/storages/KeyBuilderCS.d.ts +7 -2
  130. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +2 -2
  131. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +1 -1
  132. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +3 -2
  133. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +4 -6
  134. package/types/storages/pluggable/inMemoryWrapper.d.ts +1 -1
  135. package/types/storages/types.d.ts +4 -3
  136. package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -3
  137. package/types/sync/polling/types.d.ts +9 -2
  138. package/types/sync/polling/updaters/mySegmentsUpdater.d.ts +4 -4
  139. package/types/sync/streaming/SSEHandler/types.d.ts +13 -2
  140. package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +2 -1
  141. package/types/sync/streaming/constants.d.ts +1 -0
  142. package/types/sync/streaming/pushManager.d.ts +2 -0
  143. package/types/sync/streaming/types.d.ts +5 -4
  144. package/types/sync/submitters/types.d.ts +9 -3
  145. package/types/types.d.ts +25 -0
  146. package/types/utils/constants/index.d.ts +3 -0
  147. package/types/utils/settingsValidation/index.d.ts +2 -0
  148. package/types/utils/settingsValidation/types.d.ts +1 -1
@@ -6,8 +6,9 @@ import { mySegmentsSyncTaskFactory } from './syncTasks/mySegmentsSyncTask';
6
6
  import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
7
7
  import { getMatching } from '../../utils/key';
8
8
  import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
9
- import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
9
+ import { POLLING_START, POLLING_STOP } from '../../logger/constants';
10
10
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
11
+ import { IN_LARGE_SEGMENT, IN_SEGMENT } from '../../utils/constants';
11
12
 
12
13
  /**
13
14
  * Expose start / stop mechanism for polling data from services.
@@ -22,62 +23,92 @@ export function pollingManagerCSFactory(
22
23
 
23
24
  const splitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings, true);
24
25
 
25
- // Map of matching keys to their corresponding MySegmentsSyncTask.
26
- const mySegmentsSyncTasks: Record<string, IMySegmentsSyncTask> = {};
26
+ // Map of matching keys to their corresponding MySegmentsSyncTask for segments and large segments.
27
+ const mySegmentsSyncTasks: Record<string, { msSyncTask: IMySegmentsSyncTask, mlsSyncTask?: IMySegmentsSyncTask }> = {};
27
28
 
28
29
  const matchingKey = getMatching(settings.core.key);
29
- const mySegmentsSyncTask = add(matchingKey, readiness, storage);
30
+ const { msSyncTask, mlsSyncTask } = add(matchingKey, readiness, storage);
30
31
 
31
32
  function startMySegmentsSyncTasks() {
32
- forOwn(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
33
- mySegmentsSyncTask.start();
33
+ const splitsHaveSegments = storage.splits.usesMatcher(IN_SEGMENT);
34
+ const splitsHaveLargeSegments = storage.splits.usesMatcher(IN_LARGE_SEGMENT);
35
+
36
+ forOwn(mySegmentsSyncTasks, ({ msSyncTask, mlsSyncTask }) => {
37
+ if (splitsHaveSegments) msSyncTask.start();
38
+ else msSyncTask.stop(); // smart pausing
39
+
40
+ if (mlsSyncTask) {
41
+ if (splitsHaveLargeSegments) mlsSyncTask.start();
42
+ else mlsSyncTask.stop(); // smart pausing
43
+ }
34
44
  });
35
45
  }
36
46
 
37
47
  function stopMySegmentsSyncTasks() {
38
- forOwn(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
39
- if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop();
48
+ forOwn(mySegmentsSyncTasks, ({ msSyncTask, mlsSyncTask }) => {
49
+ msSyncTask.stop();
50
+ mlsSyncTask && mlsSyncTask.stop();
40
51
  });
41
52
  }
42
53
 
43
- // smart pausing
44
54
  readiness.splits.on(SDK_SPLITS_ARRIVED, () => {
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) {
50
- startMySegmentsSyncTasks();
51
- } else {
52
- stopMySegmentsSyncTasks();
53
- }
54
- }
55
+ if (splitsSyncTask.isRunning()) startMySegmentsSyncTasks();
55
56
  });
56
57
 
57
58
  function add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync) {
58
- const mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMySegments, storage, readiness, settings, matchingKey);
59
+ const msSyncTask = mySegmentsSyncTaskFactory(
60
+ splitApi.fetchMySegments,
61
+ storage.segments,
62
+ () => { if (storage.splits.usesMatcher(IN_SEGMENT)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED); },
63
+ settings,
64
+ matchingKey,
65
+ settings.scheduler.segmentsRefreshRate,
66
+ 'mySegmentsUpdater'
67
+ );
68
+
69
+ let mlsSyncTask;
70
+ if (settings.sync.largeSegmentsEnabled) {
71
+ mlsSyncTask = mySegmentsSyncTaskFactory(
72
+ splitApi.fetchMyLargeSegments,
73
+ storage.largeSegments!,
74
+ () => { if (readiness.largeSegments && storage.splits.usesMatcher(IN_LARGE_SEGMENT)) readiness.largeSegments.emit(SDK_SEGMENTS_ARRIVED); },
75
+ settings,
76
+ matchingKey,
77
+ settings.scheduler.largeSegmentsRefreshRate,
78
+ 'myLargeSegmentsUpdater'
79
+ );
80
+ }
59
81
 
60
82
  // smart ready
61
83
  function smartReady() {
62
- if (!readiness.isReady() && !storage.splits.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
84
+ if (!readiness.isReady()) {
85
+ if (readiness.largeSegments && !storage.splits.usesMatcher(IN_LARGE_SEGMENT)) readiness.largeSegments.emit(SDK_SEGMENTS_ARRIVED);
86
+ if (!storage.splits.usesMatcher(IN_SEGMENT)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
87
+ }
63
88
  }
64
- if (!storage.splits.usesSegments()) setTimeout(smartReady, 0);
65
- else readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
66
89
 
67
- mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
68
- return mySegmentsSyncTask;
90
+ if (storage.splits.usesMatcher(IN_SEGMENT) && storage.splits.usesMatcher(IN_LARGE_SEGMENT)) readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
91
+ else setTimeout(smartReady, 0);
92
+
93
+ mySegmentsSyncTasks[matchingKey] = { msSyncTask: msSyncTask, mlsSyncTask: mlsSyncTask };
94
+
95
+ return {
96
+ msSyncTask,
97
+ mlsSyncTask
98
+ };
69
99
  }
70
100
 
71
101
  return {
72
102
  splitsSyncTask,
73
- segmentsSyncTask: mySegmentsSyncTask,
103
+ segmentsSyncTask: msSyncTask,
104
+ largeSegmentsSyncTask: mlsSyncTask,
74
105
 
75
106
  // Start periodic fetching (polling)
76
107
  start() {
77
108
  log.info(POLLING_START);
78
109
 
79
110
  splitsSyncTask.start();
80
- if (storage.splits.usesSegments()) startMySegmentsSyncTasks();
111
+ startMySegmentsSyncTasks();
81
112
  },
82
113
 
83
114
  // Stop periodic fetching (polling)
@@ -94,8 +125,9 @@ export function pollingManagerCSFactory(
94
125
  // fetch splits and segments
95
126
  syncAll() {
96
127
  const promises = [splitsSyncTask.execute()];
97
- forOwn(mySegmentsSyncTasks, function (mySegmentsSyncTask) {
98
- promises.push(mySegmentsSyncTask.execute());
128
+ forOwn(mySegmentsSyncTasks, function ({ msSyncTask, mlsSyncTask }) {
129
+ promises.push(msSyncTask.execute());
130
+ mlsSyncTask && promises.push(mlsSyncTask.execute());
99
131
  });
100
132
  return Promise.all(promises);
101
133
  },
@@ -1,5 +1,4 @@
1
- import { IStorageSync } from '../../../storages/types';
2
- import { IReadinessManager } from '../../../readiness/types';
1
+ import { ISegmentsCacheSync } from '../../../storages/types';
3
2
  import { syncTaskFactory } from '../../syncTask';
4
3
  import { IMySegmentsSyncTask } from '../types';
5
4
  import { IFetchMySegments } from '../../../services/types';
@@ -12,24 +11,25 @@ import { mySegmentsUpdaterFactory } from '../updaters/mySegmentsUpdater';
12
11
  */
13
12
  export function mySegmentsSyncTaskFactory(
14
13
  fetchMySegments: IFetchMySegments,
15
- storage: IStorageSync,
16
- readiness: IReadinessManager,
14
+ mySegmentsCache: ISegmentsCacheSync,
15
+ notifyUpdate: () => void,
17
16
  settings: ISettings,
18
- matchingKey: string
17
+ matchingKey: string,
18
+ segmentsRefreshRate: number,
19
+ NAME: string
19
20
  ): IMySegmentsSyncTask {
20
21
  return syncTaskFactory(
21
22
  settings.log,
22
23
  mySegmentsUpdaterFactory(
23
24
  settings.log,
24
25
  mySegmentsFetcherFactory(fetchMySegments),
25
- storage.splits,
26
- storage.segments,
27
- readiness.segments,
26
+ mySegmentsCache,
27
+ notifyUpdate,
28
28
  settings.startup.requestTimeoutBeforeReady,
29
29
  settings.startup.retriesOnFailureBeforeReady,
30
30
  matchingKey
31
31
  ),
32
- settings.scheduler.segmentsRefreshRate,
33
- 'mySegmentsUpdater',
32
+ segmentsRefreshRate,
33
+ NAME,
34
34
  );
35
35
  }
@@ -20,13 +20,14 @@ export interface IPollingManager extends ITask {
20
20
  syncAll(): Promise<any>
21
21
  splitsSyncTask: ISplitsSyncTask
22
22
  segmentsSyncTask: ISyncTask
23
+ largeSegmentsSyncTask?: ISyncTask
23
24
  }
24
25
 
25
26
  /**
26
27
  * PollingManager for client-side with support for multiple clients
27
28
  */
28
29
  export interface IPollingManagerCS extends IPollingManager {
29
- add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync): IMySegmentsSyncTask
30
+ add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync): { msSyncTask: IMySegmentsSyncTask, mlsSyncTask?: IMySegmentsSyncTask }
30
31
  remove(matchingKey: string): void;
31
- get(matchingKey: string): IMySegmentsSyncTask | undefined
32
+ get(matchingKey: string): { msSyncTask: IMySegmentsSyncTask, mlsSyncTask?: IMySegmentsSyncTask } | undefined
32
33
  }
@@ -1,13 +1,11 @@
1
1
  import { IMySegmentsFetcher } from '../fetchers/types';
2
- import { ISegmentsCacheSync, ISplitsCacheSync } from '../../../storages/types';
3
- import { ISegmentsEventEmitter } from '../../../readiness/types';
2
+ import { ISegmentsCacheSync } from '../../../storages/types';
4
3
  import { timeout } from '../../../utils/promise/timeout';
5
- import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
6
4
  import { ILogger } from '../../../logger/types';
7
5
  import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants';
8
6
  import { MySegmentsData } from '../types';
9
7
 
10
- type IMySegmentsUpdater = (segmentList?: string[], noCache?: boolean) => Promise<boolean>
8
+ type IMySegmentsUpdater = (segmentList?: MySegmentsData, noCache?: boolean) => Promise<boolean>
11
9
 
12
10
  /**
13
11
  * factory of MySegments updater, a task that:
@@ -18,9 +16,8 @@ type IMySegmentsUpdater = (segmentList?: string[], noCache?: boolean) => Promise
18
16
  export function mySegmentsUpdaterFactory(
19
17
  log: ILogger,
20
18
  mySegmentsFetcher: IMySegmentsFetcher,
21
- splitsCache: ISplitsCacheSync,
22
19
  mySegmentsCache: ISegmentsCacheSync,
23
- segmentsEventEmitter: ISegmentsEventEmitter,
20
+ notifyUpdate: () => void,
24
21
  requestTimeoutBeforeReady: number,
25
22
  retriesOnFailureBeforeReady: number,
26
23
  matchingKey: string
@@ -55,9 +52,9 @@ export function mySegmentsUpdaterFactory(
55
52
  }
56
53
 
57
54
  // Notify update if required
58
- if (splitsCache.usesSegments() && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
55
+ if (shouldNotifyUpdate || readyOnAlreadyExistentState) {
59
56
  readyOnAlreadyExistentState = false;
60
- segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
57
+ notifyUpdate();
61
58
  }
62
59
  }
63
60
 
@@ -8,6 +8,7 @@ import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/
8
8
  import { ILogger } from '../../../logger/types';
9
9
  import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
10
10
  import { startsWith } from '../../../utils/lang';
11
+ import { IN_SEGMENT } from '../../../utils/constants';
11
12
 
12
13
  type ISplitChangesUpdater = (noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }) => Promise<boolean>
13
14
 
@@ -33,7 +34,7 @@ export function parseSegments({ conditions }: ISplit): ISet<string> {
33
34
  const matchers = conditions[i].matcherGroup.matchers;
34
35
 
35
36
  matchers.forEach(matcher => {
36
- if (matcher.matcherType === 'IN_SEGMENT') segments.add(matcher.userDefinedSegmentMatcherData.segmentName);
37
+ if (matcher.matcherType === IN_SEGMENT) segments.add(matcher.userDefinedSegmentMatcherData.segmentName);
37
38
  });
38
39
  }
39
40
 
@@ -54,7 +55,7 @@ interface ISplitMutations {
54
55
  * @param filters splitFiltersValidation bySet | byName
55
56
  */
56
57
  function matchFilters(featureFlag: ISplit, filters: ISplitFiltersValidation) {
57
- const { bySet: setsFilter, byName: namesFilter, byPrefix: prefixFilter} = filters.groupedFilters;
58
+ const { bySet: setsFilter, byName: namesFilter, byPrefix: prefixFilter } = filters.groupedFilters;
58
59
  if (setsFilter.length > 0) return featureFlag.sets && featureFlag.sets.some((featureFlagSet: string) => setsFilter.indexOf(featureFlagSet) > -1);
59
60
 
60
61
  const namesFilterConfigured = namesFilter.length > 0;
@@ -129,7 +130,7 @@ export function splitChangesUpdaterFactory(
129
130
 
130
131
  /** Returns true if at least one split was updated */
131
132
  function isThereUpdate(flagsChange: [boolean | void, void | boolean[], void | boolean[], boolean | void] | [any, any, any]) {
132
- const [, added, removed, ] = flagsChange;
133
+ const [, added, removed] = flagsChange;
133
134
  // There is at least one added or modified feature flag
134
135
  if (added && added.some((update: boolean) => update)) return true;
135
136
  // There is at least one removed feature flag
@@ -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, MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE } from '../constants';
3
+ import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MY_LARGE_SEGMENTS_UPDATE } from '../constants';
4
4
  import { IPushEventEmitter } from '../types';
5
5
  import { ISseEventHandler } from '../SSEClient/types';
6
6
  import { INotificationError, INotificationMessage } from './types';
@@ -83,6 +83,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
83
83
  case SPLIT_UPDATE:
84
84
  case SEGMENT_UPDATE:
85
85
  case MY_SEGMENTS_UPDATE_V2:
86
+ case MY_LARGE_SEGMENTS_UPDATE:
86
87
  case SPLIT_KILL:
87
88
  pushEmitter.emit(parsedData.type, parsedData);
88
89
  break;
@@ -1,5 +1,5 @@
1
1
  import { ControlType } from '../constants';
2
- import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY } from '../types';
2
+ import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MY_LARGE_SEGMENTS_UPDATE } from '../types';
3
3
 
4
4
  export interface IMySegmentsUpdateData {
5
5
  type: MY_SEGMENTS_UPDATE,
@@ -35,6 +35,18 @@ export interface IMySegmentsUpdateV2Data {
35
35
  u: UpdateStrategy,
36
36
  }
37
37
 
38
+ export interface IMyLargeSegmentsUpdateData {
39
+ type: MY_LARGE_SEGMENTS_UPDATE,
40
+ changeNumber: number,
41
+ largeSegments: string[],
42
+ c: Compression,
43
+ d: string,
44
+ u: UpdateStrategy,
45
+ i?: number, // time interval in millis
46
+ h?: number, // hash function. 0 for murmur3_32, 1 for murmur3_64
47
+ s?: number, // seed for hash function
48
+ }
49
+
38
50
  export interface ISegmentUpdateData {
39
51
  type: SEGMENT_UPDATE,
40
52
  changeNumber: number,
@@ -68,6 +80,6 @@ export interface IOccupancyData {
68
80
  }
69
81
  }
70
82
 
71
- export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
83
+ export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | IMyLargeSegmentsUpdateData | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
72
84
  export type INotificationMessage = { parsedData: INotificationData, channel: string, timestamp: number, data: string }
73
85
  export type INotificationError = Event & { parsedData?: any, message?: string }
@@ -1,19 +1,21 @@
1
1
  import { IMySegmentsSyncTask, MySegmentsData } from '../../polling/types';
2
2
  import { Backoff } from '../../../utils/Backoff';
3
3
  import { IUpdateWorker } from './types';
4
- import { MY_SEGMENT } from '../../../utils/constants';
5
4
  import { ITelemetryTracker } from '../../../trackers/types';
5
+ import { UpdatesFromSSEEnum } from '../../submitters/types';
6
6
 
7
7
  /**
8
8
  * MySegmentsUpdateWorker factory
9
9
  */
10
- export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask, telemetryTracker: ITelemetryTracker): IUpdateWorker {
10
+ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask, telemetryTracker: ITelemetryTracker, updateType: UpdatesFromSSEEnum): IUpdateWorker {
11
11
 
12
12
  let maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
13
13
  let currentChangeNumber = -1;
14
14
  let handleNewEvent = false;
15
15
  let isHandlingEvent: boolean;
16
16
  let _segmentsData: MySegmentsData | undefined; // keeps the segmentsData (if included in notification payload) from the queued event with maximum changeNumber
17
+ let _delay: undefined | number;
18
+ let _delayTimeoutID: undefined | number;
17
19
  const backoff = new Backoff(__handleMySegmentsUpdateCall);
18
20
 
19
21
  function __handleMySegmentsUpdateCall() {
@@ -23,10 +25,18 @@ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask,
23
25
  const currentMaxChangeNumber = maxChangeNumber;
24
26
 
25
27
  // fetch mySegments revalidating data if cached
26
- mySegmentsSyncTask.execute(_segmentsData, true).then((result) => {
28
+ const syncTask = _delay ?
29
+ new Promise(res => {
30
+ _delayTimeoutID = setTimeout(() => {
31
+ mySegmentsSyncTask.execute(_segmentsData, true).then(res);
32
+ }, _delay);
33
+ }) :
34
+ mySegmentsSyncTask.execute(_segmentsData, true);
35
+
36
+ syncTask.then((result) => {
27
37
  if (!isHandlingEvent) return; // halt if `stop` has been called
28
38
  if (result !== false) {// Unlike `Splits|SegmentsUpdateWorker`, we cannot use `mySegmentsCache.getChangeNumber` since `/mySegments` endpoint doesn't provide this value.
29
- if (_segmentsData) telemetryTracker.trackUpdatesFromSSE(MY_SEGMENT);
39
+ if (_segmentsData) telemetryTracker.trackUpdatesFromSSE(updateType);
30
40
  currentChangeNumber = Math.max(currentChangeNumber, currentMaxChangeNumber); // use `currentMaxChangeNumber`, in case that `maxChangeNumber` was updated during fetch.
31
41
  }
32
42
  if (handleNewEvent) {
@@ -47,18 +57,20 @@ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask,
47
57
  * @param {number} changeNumber change number of the MY_SEGMENTS_UPDATE notification
48
58
  * @param {SegmentsData | undefined} segmentsData might be undefined
49
59
  */
50
- put(changeNumber: number, segmentsData?: MySegmentsData) {
60
+ put(changeNumber: number, segmentsData?: MySegmentsData, delay?: number) {
51
61
  if (changeNumber <= currentChangeNumber || changeNumber <= maxChangeNumber) return;
52
62
 
53
63
  maxChangeNumber = changeNumber;
54
64
  handleNewEvent = true;
55
65
  _segmentsData = segmentsData;
66
+ _delay = delay;
56
67
 
57
68
  if (backoff.timeoutID || !isHandlingEvent) __handleMySegmentsUpdateCall();
58
69
  backoff.reset();
59
70
  },
60
71
 
61
72
  stop() {
73
+ clearTimeout(_delayTimeoutID);
62
74
  isHandlingEvent = false;
63
75
  backoff.reset();
64
76
  }
@@ -30,6 +30,7 @@ export const MY_SEGMENTS_UPDATE_V2 = 'MY_SEGMENTS_UPDATE_V2';
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 MY_LARGE_SEGMENTS_UPDATE = 'MY_LARGE_SEGMENTS_UPDATE';
33
34
 
34
35
  // Control-type push notifications, handled by NotificationKeeper
35
36
  export const CONTROL = 'CONTROL';
@@ -11,17 +11,25 @@ import { authenticateFactory, hashUserKey } from './AuthClient';
11
11
  import { forOwn } from '../../utils/lang';
12
12
  import { SSEClient } from './SSEClient';
13
13
  import { getMatching } from '../../utils/key';
14
- import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
14
+ import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType, MY_LARGE_SEGMENTS_UPDATE } from './constants';
15
15
  import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, STREAMING_PARSING_SPLIT_UPDATE } from '../../logger/constants';
16
- import { KeyList, UpdateStrategy } from './SSEHandler/types';
16
+ import { IMyLargeSegmentsUpdateData, IMySegmentsUpdateV2Data, KeyList, UpdateStrategy } from './SSEHandler/types';
17
17
  import { isInBitmap, parseBitmap, parseFFUpdatePayload, parseKeyList } from './parseUtils';
18
18
  import { ISet, _Set } from '../../utils/lang/sets';
19
+ import { hash } from '../../utils/murmur3/murmur3';
19
20
  import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
20
21
  import { IAuthTokenPushEnabled } from './AuthClient/types';
21
- import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
22
+ import { TOKEN_REFRESH, AUTH_REJECTION, MY_LARGE_SEGMENT, MY_SEGMENT } from '../../utils/constants';
22
23
  import { ISdkFactoryContextSync } from '../../sdkFactory/types';
23
24
  import { IUpdateWorker } from './UpdateWorkers/types';
24
25
 
26
+ export function getDelay(parsedData: Pick<IMyLargeSegmentsUpdateData, 'i' | 'h' | 's'>, matchingKey: string) {
27
+ const interval = parsedData.i || 60000;
28
+ const seed = parsedData.s || 0;
29
+
30
+ return hash(matchingKey, seed) % interval;
31
+ }
32
+
25
33
  /**
26
34
  * PushManager factory:
27
35
  * - for server-side if key is not provided in settings.
@@ -64,7 +72,7 @@ export function pushManagerFactory(
64
72
  const userKeyHashes: Record<string, string> = {};
65
73
  // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
66
74
  // Hash64 is used to process MY_SEGMENTS_UPDATE_V2 events and dispatch actions to the corresponding MySegmentsUpdateWorker.
67
- const clients: Record<string, { hash64: Hash64, worker: IUpdateWorker }> = {};
75
+ const clients: Record<string, { hash64: Hash64, worker: IUpdateWorker, workerLarge?: IUpdateWorker }> = {};
68
76
 
69
77
  // [Only for client-side] variable to flag that a new client was added. It is needed to reconnect streaming.
70
78
  let connectForNewClient = false;
@@ -171,7 +179,10 @@ export function pushManagerFactory(
171
179
  // cancel scheduled fetch retries of Splits, Segments, and MySegments Update Workers
172
180
  function stopWorkers() {
173
181
  splitsUpdateWorker.stop();
174
- if (userKey) forOwn(clients, ({ worker }) => worker.stop());
182
+ if (userKey) forOwn(clients, ({ worker, workerLarge }) => {
183
+ worker.stop();
184
+ workerLarge && workerLarge.stop();
185
+ });
175
186
  else segmentsUpdateWorker!.stop();
176
187
  }
177
188
 
@@ -236,76 +247,98 @@ export function pushManagerFactory(
236
247
  splitsUpdateWorker.put(parsedData);
237
248
  });
238
249
 
239
- if (userKey) {
240
- pushEmitter.on(MY_SEGMENTS_UPDATE, function handleMySegmentsUpdate(parsedData, channel) {
241
- const userKeyHash = channel.split('_')[2];
242
- const userKey = userKeyHashes[userKeyHash];
243
- if (userKey && clients[userKey]) { // check existence since it can be undefined if client has been destroyed
244
- clients[userKey].worker.put(
245
- parsedData.changeNumber,
246
- parsedData.includesPayload ? parsedData.segmentList ? parsedData.segmentList : [] : undefined);
247
- }
248
- });
249
- pushEmitter.on(MY_SEGMENTS_UPDATE_V2, function handleMySegmentsUpdate(parsedData) {
250
- switch (parsedData.u) {
251
- case UpdateStrategy.BoundedFetchRequest: {
252
- let bitmap: Uint8Array;
253
- try {
254
- bitmap = parseBitmap(parsedData.d, parsedData.c);
255
- } catch (e) {
256
- log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['BoundedFetchRequest', e]);
257
- break;
258
- }
259
-
260
- forOwn(clients, ({ hash64, worker }) => {
261
- if (isInBitmap(bitmap, hash64.hex)) {
262
- worker.put(parsedData.changeNumber); // fetch mySegments
263
- }
264
- });
265
- return;
250
+ function handleMySegmentsUpdate(parsedData: IMySegmentsUpdateV2Data | IMyLargeSegmentsUpdateData) {
251
+ const isLS = parsedData.type === MY_LARGE_SEGMENTS_UPDATE;
252
+
253
+ switch (parsedData.u) {
254
+ case UpdateStrategy.BoundedFetchRequest: {
255
+ let bitmap: Uint8Array;
256
+ try {
257
+ bitmap = parseBitmap(parsedData.d, parsedData.c);
258
+ } catch (e) {
259
+ log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['BoundedFetchRequest', e]);
260
+ break;
266
261
  }
267
- case UpdateStrategy.KeyList: {
268
- let keyList: KeyList, added: ISet<string>, removed: ISet<string>;
269
- try {
270
- keyList = parseKeyList(parsedData.d, parsedData.c);
271
- added = new _Set(keyList.a);
272
- removed = new _Set(keyList.r);
273
- } catch (e) {
274
- log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['KeyList', e]);
275
- break;
262
+
263
+ forOwn(clients, ({ hash64, worker, workerLarge }, matchingKey) => {
264
+ if (isInBitmap(bitmap, hash64.hex)) {
265
+ isLS ?
266
+ workerLarge && workerLarge.put(parsedData.changeNumber, undefined, getDelay(parsedData, matchingKey)) :
267
+ worker.put(parsedData.changeNumber);
276
268
  }
269
+ });
270
+ return;
271
+ }
272
+ case UpdateStrategy.KeyList: {
273
+ let keyList: KeyList, added: ISet<string>, removed: ISet<string>;
274
+ try {
275
+ keyList = parseKeyList(parsedData.d, parsedData.c);
276
+ added = new _Set(keyList.a);
277
+ removed = new _Set(keyList.r);
278
+ } catch (e) {
279
+ log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['KeyList', e]);
280
+ break;
281
+ }
277
282
 
278
- forOwn(clients, ({ hash64, worker }) => {
279
- const add = added.has(hash64.dec) ? true : removed.has(hash64.dec) ? false : undefined;
280
- if (add !== undefined) {
283
+ forOwn(clients, ({ hash64, worker, workerLarge }) => {
284
+ const add = added.has(hash64.dec) ? true : removed.has(hash64.dec) ? false : undefined;
285
+ if (add !== undefined) {
286
+ isLS ?
287
+ workerLarge && workerLarge.put(parsedData.changeNumber, {
288
+ name: parsedData.largeSegments[0],
289
+ add
290
+ }) :
281
291
  worker.put(parsedData.changeNumber, {
282
292
  name: parsedData.segmentName,
283
293
  add
284
294
  });
285
- }
286
- });
287
- return;
288
- }
289
- case UpdateStrategy.SegmentRemoval:
290
- if (!parsedData.segmentName) {
291
- log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['SegmentRemoval', 'No segment name was provided']);
292
- break;
293
295
  }
296
+ });
297
+ return;
298
+ }
299
+ case UpdateStrategy.SegmentRemoval:
300
+ if ((isLS && parsedData.largeSegments.length === 0) || (!isLS && !parsedData.segmentName)) {
301
+ log.warn(STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, ['SegmentRemoval', 'No segment name was provided']);
302
+ break;
303
+ }
294
304
 
295
- forOwn(clients, ({ worker }) =>
305
+ forOwn(clients, ({ worker, workerLarge }) => {
306
+ isLS ?
307
+ workerLarge && parsedData.largeSegments.forEach(largeSegment => {
308
+ workerLarge.put(parsedData.changeNumber, {
309
+ name: largeSegment,
310
+ add: false
311
+ });
312
+ }) :
296
313
  worker.put(parsedData.changeNumber, {
297
314
  name: parsedData.segmentName,
298
315
  add: false
299
- })
300
- );
301
- return;
302
- }
316
+ });
317
+ });
318
+ return;
319
+ }
303
320
 
304
- // `UpdateStrategy.UnboundedFetchRequest` and fallbacks of other cases
305
- forOwn(clients, ({ worker }) => {
321
+ // `UpdateStrategy.UnboundedFetchRequest` and fallbacks of other cases
322
+ forOwn(clients, ({ worker, workerLarge }, matchingKey) => {
323
+ isLS ?
324
+ workerLarge && workerLarge.put(parsedData.changeNumber, undefined, getDelay(parsedData, matchingKey)) :
306
325
  worker.put(parsedData.changeNumber);
307
- });
308
326
  });
327
+ }
328
+
329
+ if (userKey) {
330
+ pushEmitter.on(MY_SEGMENTS_UPDATE, function handleMySegmentsUpdate(parsedData, channel) {
331
+ const userKeyHash = channel.split('_')[2];
332
+ const userKey = userKeyHashes[userKeyHash];
333
+ if (userKey && clients[userKey]) { // check existence since it can be undefined if client has been destroyed
334
+ clients[userKey].worker.put(
335
+ parsedData.changeNumber,
336
+ parsedData.includesPayload ? parsedData.segmentList ? parsedData.segmentList : [] : undefined);
337
+ }
338
+ });
339
+
340
+ pushEmitter.on(MY_SEGMENTS_UPDATE_V2, handleMySegmentsUpdate);
341
+ pushEmitter.on(MY_LARGE_SEGMENTS_UPDATE, handleMySegmentsUpdate);
309
342
  } else {
310
343
  pushEmitter.on(SEGMENT_UPDATE, segmentsUpdateWorker!.put);
311
344
  }
@@ -328,7 +361,7 @@ export function pushManagerFactory(
328
361
  if (disabled || disconnected === false) return;
329
362
  disconnected = false;
330
363
 
331
- if (userKey) this.add(userKey, pollingManager.segmentsSyncTask as IMySegmentsSyncTask); // client-side
364
+ if (userKey) this.add(userKey, pollingManager.segmentsSyncTask, pollingManager.largeSegmentsSyncTask!); // client-side
332
365
  else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side
333
366
  },
334
367
 
@@ -338,12 +371,16 @@ export function pushManagerFactory(
338
371
  },
339
372
 
340
373
  // [Only for client-side]
341
- add(userKey: string, mySegmentsSyncTask: IMySegmentsSyncTask) {
374
+ add(userKey: string, mySegmentsSyncTask: IMySegmentsSyncTask, myLargeSegmentsSyncTask?: IMySegmentsSyncTask) {
342
375
  const hash = hashUserKey(userKey);
343
376
 
344
377
  if (!userKeyHashes[hash]) {
345
378
  userKeyHashes[hash] = userKey;
346
- clients[userKey] = { hash64: hash64(userKey), worker: MySegmentsUpdateWorker(mySegmentsSyncTask, telemetryTracker) };
379
+ clients[userKey] = {
380
+ hash64: hash64(userKey),
381
+ worker: MySegmentsUpdateWorker(mySegmentsSyncTask, telemetryTracker, MY_SEGMENT),
382
+ workerLarge: myLargeSegmentsSyncTask ? MySegmentsUpdateWorker(myLargeSegmentsSyncTask, telemetryTracker, MY_LARGE_SEGMENT) : undefined
383
+ };
347
384
  connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key
348
385
 
349
386
  // Reconnects in case of a new client.