@splitsoftware/splitio-commons 1.17.0 → 1.17.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 (181) hide show
  1. package/CHANGES.txt +8 -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 +4 -1
  6. package/cjs/evaluator/matchersTransform/segment.js +3 -1
  7. package/cjs/logger/constants.js +2 -2
  8. package/cjs/logger/messages/info.js +1 -1
  9. package/cjs/logger/messages/warn.js +1 -1
  10. package/cjs/readiness/readinessManager.js +5 -6
  11. package/cjs/readiness/sdkReadinessManager.js +5 -6
  12. package/cjs/sdkClient/identity.js +7 -0
  13. package/cjs/sdkClient/sdkClient.js +5 -5
  14. package/cjs/sdkClient/sdkClientMethod.js +3 -1
  15. package/cjs/sdkClient/sdkClientMethodCS.js +9 -14
  16. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +9 -14
  17. package/cjs/sdkFactory/index.js +6 -2
  18. package/cjs/services/splitApi.js +5 -5
  19. package/cjs/storages/AbstractSegmentsCacheSync.js +41 -12
  20. package/cjs/storages/AbstractSplitsCacheSync.js +2 -1
  21. package/cjs/storages/KeyBuilderCS.js +23 -5
  22. package/cjs/storages/dataLoader.js +1 -1
  23. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +29 -52
  24. package/cjs/storages/inLocalStorage/index.js +6 -2
  25. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -0
  26. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +9 -40
  27. package/cjs/storages/inMemory/SplitsCacheInMemory.js +8 -8
  28. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +7 -10
  29. package/cjs/storages/pluggable/inMemoryWrapper.js +1 -1
  30. package/cjs/sync/polling/fetchers/mySegmentsFetcher.js +5 -8
  31. package/cjs/sync/polling/fetchers/segmentChangesFetcher.js +1 -1
  32. package/cjs/sync/polling/pollingManagerCS.js +1 -1
  33. package/cjs/sync/polling/syncTasks/mySegmentsSyncTask.js +2 -2
  34. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +15 -21
  35. package/cjs/sync/streaming/AuthClient/index.js +1 -1
  36. package/cjs/sync/streaming/SSEHandler/index.js +3 -5
  37. package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +107 -48
  38. package/cjs/sync/streaming/constants.js +3 -3
  39. package/cjs/sync/streaming/parseUtils.js +14 -9
  40. package/cjs/sync/streaming/pushManager.js +69 -67
  41. package/cjs/utils/constants/index.js +5 -4
  42. package/cjs/utils/settingsValidation/index.js +2 -1
  43. package/esm/evaluator/matchers/index.js +3 -1
  44. package/esm/evaluator/matchers/large_segment.js +12 -0
  45. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  46. package/esm/evaluator/matchersTransform/index.js +4 -1
  47. package/esm/evaluator/matchersTransform/segment.js +3 -1
  48. package/esm/logger/constants.js +1 -1
  49. package/esm/logger/messages/info.js +1 -1
  50. package/esm/logger/messages/warn.js +1 -1
  51. package/esm/readiness/readinessManager.js +5 -6
  52. package/esm/readiness/sdkReadinessManager.js +5 -6
  53. package/esm/sdkClient/identity.js +3 -0
  54. package/esm/sdkClient/sdkClient.js +5 -5
  55. package/esm/sdkClient/sdkClientMethod.js +3 -1
  56. package/esm/sdkClient/sdkClientMethodCS.js +7 -12
  57. package/esm/sdkClient/sdkClientMethodCSWithTT.js +7 -12
  58. package/esm/sdkFactory/index.js +6 -2
  59. package/esm/services/splitApi.js +6 -6
  60. package/esm/storages/AbstractSegmentsCacheSync.js +41 -12
  61. package/esm/storages/AbstractSplitsCacheSync.js +3 -2
  62. package/esm/storages/KeyBuilderCS.js +21 -4
  63. package/esm/storages/dataLoader.js +1 -1
  64. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +29 -52
  65. package/esm/storages/inLocalStorage/index.js +7 -3
  66. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -0
  67. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +9 -40
  68. package/esm/storages/inMemory/SplitsCacheInMemory.js +8 -8
  69. package/esm/storages/inMemory/TelemetryCacheInMemory.js +7 -10
  70. package/esm/storages/pluggable/inMemoryWrapper.js +1 -1
  71. package/esm/sync/polling/fetchers/mySegmentsFetcher.js +5 -8
  72. package/esm/sync/polling/fetchers/segmentChangesFetcher.js +1 -1
  73. package/esm/sync/polling/pollingManagerCS.js +1 -1
  74. package/esm/sync/polling/syncTasks/mySegmentsSyncTask.js +2 -2
  75. package/esm/sync/polling/updaters/mySegmentsUpdater.js +15 -21
  76. package/esm/sync/streaming/AuthClient/index.js +1 -1
  77. package/esm/sync/streaming/SSEHandler/index.js +4 -6
  78. package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +108 -49
  79. package/esm/sync/streaming/constants.js +2 -2
  80. package/esm/sync/streaming/parseUtils.js +12 -8
  81. package/esm/sync/streaming/pushManager.js +72 -70
  82. package/esm/utils/constants/index.js +3 -2
  83. package/esm/utils/settingsValidation/index.js +2 -1
  84. package/package.json +1 -1
  85. package/src/dtos/types.ts +21 -7
  86. package/src/evaluator/matchers/index.ts +2 -0
  87. package/src/evaluator/matchers/large_segment.ts +18 -0
  88. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  89. package/src/evaluator/matchersTransform/index.ts +4 -1
  90. package/src/evaluator/matchersTransform/segment.ts +5 -3
  91. package/src/logger/constants.ts +1 -1
  92. package/src/logger/messages/info.ts +1 -1
  93. package/src/logger/messages/warn.ts +1 -1
  94. package/src/readiness/readinessManager.ts +7 -5
  95. package/src/readiness/sdkReadinessManager.ts +7 -7
  96. package/src/readiness/types.ts +2 -2
  97. package/src/sdkClient/identity.ts +5 -0
  98. package/src/sdkClient/sdkClient.ts +5 -5
  99. package/src/sdkClient/sdkClientMethod.ts +4 -1
  100. package/src/sdkClient/sdkClientMethodCS.ts +7 -13
  101. package/src/sdkClient/sdkClientMethodCSWithTT.ts +7 -13
  102. package/src/sdkFactory/index.ts +8 -4
  103. package/src/sdkFactory/types.ts +2 -1
  104. package/src/services/splitApi.ts +7 -7
  105. package/src/services/splitHttpClient.ts +1 -1
  106. package/src/services/types.ts +2 -2
  107. package/src/storages/AbstractSegmentsCacheSync.ts +53 -12
  108. package/src/storages/AbstractSplitsCacheSync.ts +4 -3
  109. package/src/storages/KeyBuilderCS.ts +34 -5
  110. package/src/storages/dataLoader.ts +1 -1
  111. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +29 -59
  112. package/src/storages/inLocalStorage/index.ts +8 -4
  113. package/src/storages/inMemory/InMemoryStorageCS.ts +5 -0
  114. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +10 -44
  115. package/src/storages/inMemory/SplitsCacheInMemory.ts +7 -8
  116. package/src/storages/inMemory/TelemetryCacheInMemory.ts +7 -11
  117. package/src/storages/pluggable/inMemoryWrapper.ts +1 -1
  118. package/src/storages/types.ts +11 -7
  119. package/src/sync/polling/fetchers/mySegmentsFetcher.ts +8 -10
  120. package/src/sync/polling/fetchers/segmentChangesFetcher.ts +1 -1
  121. package/src/sync/polling/fetchers/types.ts +3 -2
  122. package/src/sync/polling/pollingManagerCS.ts +4 -4
  123. package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +4 -5
  124. package/src/sync/polling/types.ts +7 -6
  125. package/src/sync/polling/updaters/mySegmentsUpdater.ts +19 -22
  126. package/src/sync/streaming/AuthClient/index.ts +1 -1
  127. package/src/sync/streaming/SSEClient/index.ts +4 -6
  128. package/src/sync/streaming/SSEHandler/index.ts +5 -8
  129. package/src/sync/streaming/SSEHandler/types.ts +15 -15
  130. package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +116 -49
  131. package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +1 -1
  132. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +1 -1
  133. package/src/sync/streaming/UpdateWorkers/types.ts +2 -2
  134. package/src/sync/streaming/constants.ts +2 -2
  135. package/src/sync/streaming/parseUtils.ts +19 -11
  136. package/src/sync/streaming/pushManager.ts +73 -72
  137. package/src/sync/streaming/types.ts +10 -10
  138. package/src/sync/submitters/types.ts +8 -5
  139. package/src/types.ts +7 -1
  140. package/src/utils/constants/index.ts +3 -2
  141. package/src/utils/settingsValidation/index.ts +3 -2
  142. package/src/utils/settingsValidation/types.ts +1 -1
  143. package/types/dtos/types.d.ts +18 -7
  144. package/types/evaluator/matchersTransform/segment.d.ts +2 -2
  145. package/types/logger/constants.d.ts +1 -1
  146. package/types/readiness/readinessManager.d.ts +2 -2
  147. package/types/readiness/sdkReadinessManager.d.ts +2 -3
  148. package/types/readiness/types.d.ts +2 -2
  149. package/types/sdkClient/identity.d.ts +0 -4
  150. package/types/sdkClient/sdkClientMethod.d.ts +1 -1
  151. package/types/sdkFactory/types.d.ts +2 -1
  152. package/types/services/splitApi.d.ts +1 -1
  153. package/types/services/splitHttpClient.d.ts +1 -1
  154. package/types/services/types.d.ts +2 -2
  155. package/types/storages/AbstractMySegmentsCacheSync.d.ts +39 -0
  156. package/types/storages/AbstractSegmentsCacheSync.d.ts +9 -11
  157. package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
  158. package/types/storages/KeyBuilderCS.d.ts +9 -2
  159. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +4 -14
  160. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +3 -9
  161. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +1 -1
  162. package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +4 -6
  163. package/types/storages/pluggable/inMemoryWrapper.d.ts +1 -1
  164. package/types/storages/types.d.ts +7 -5
  165. package/types/sync/polling/fetchers/mySegmentsFetcher.d.ts +2 -2
  166. package/types/sync/polling/fetchers/types.d.ts +2 -2
  167. package/types/sync/polling/syncTasks/mySegmentsSyncTask.d.ts +2 -2
  168. package/types/sync/polling/types.d.ts +7 -4
  169. package/types/sync/polling/updaters/mySegmentsUpdater.d.ts +4 -3
  170. package/types/sync/streaming/SSEHandler/types.d.ts +16 -14
  171. package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +4 -2
  172. package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +2 -1
  173. package/types/sync/streaming/UpdateWorkers/SplitsUpdateWorker.d.ts +3 -2
  174. package/types/sync/streaming/UpdateWorkers/types.d.ts +2 -2
  175. package/types/sync/streaming/constants.d.ts +2 -2
  176. package/types/sync/streaming/parseUtils.d.ts +4 -5
  177. package/types/sync/streaming/types.d.ts +8 -8
  178. package/types/sync/submitters/types.d.ts +7 -4
  179. package/types/types.d.ts +7 -1
  180. package/types/utils/constants/index.d.ts +3 -2
  181. package/types/utils/settingsValidation/types.d.ts +1 -1
@@ -9,11 +9,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
9
9
  import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING, LOG_PREFIX_CLIENT_INSTANTIATION } from '../logger/constants';
10
10
  import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
11
11
  import { ISdkFactoryContext } from '../sdkFactory/types';
12
-
13
- function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
14
- // @ts-ignore
15
- return `${key.matchingKey ? key.matchingKey : key}-${key.bucketingKey ? key.bucketingKey : key}-${trafficType !== undefined ? trafficType : ''}`;
16
- }
12
+ import { buildInstanceId } from './identity';
17
13
 
18
14
  /**
19
15
  * Factory of client method for the client-side (browser) variant of the Isomorphic JS SDK,
@@ -21,7 +17,7 @@ function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
21
17
  * (default client) or the client method (shared clients).
22
18
  */
23
19
  export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
24
- const { storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, startup: { readyTimeout }, log } } = params;
20
+ const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, log } } = params;
25
21
 
26
22
  const mainClientInstance = clientCSDecorator(
27
23
  log,
@@ -34,8 +30,7 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
34
30
  const defaultInstanceId = buildInstanceId(parsedDefaultKey, trafficType);
35
31
 
36
32
  // Cache instances created per factory.
37
- const clientInstances: Record<string, SplitIO.ICsClient> = {};
38
- clientInstances[defaultInstanceId] = mainClientInstance;
33
+ clients[defaultInstanceId] = mainClientInstance;
39
34
 
40
35
  return function client(key?: SplitIO.SplitKey, trafficType?: string) {
41
36
  if (key === undefined) {
@@ -58,10 +53,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
58
53
  }
59
54
  const instanceId = buildInstanceId(validKey, validTrafficType);
60
55
 
61
- if (!clientInstances[instanceId]) {
56
+ if (!clients[instanceId]) {
62
57
  const matchingKey = getMatching(validKey);
63
58
 
64
- const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
59
+ const sharedSdkReadiness = sdkReadinessManager.shared();
65
60
  const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => {
66
61
  if (err) {
67
62
  sharedSdkReadiness.readinessManager.timeout();
@@ -80,13 +75,12 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
80
75
 
81
76
  // As shared clients reuse all the storage information, we don't need to check here if we
82
77
  // will use offline or online mode. We should stick with the original decision.
83
- clientInstances[instanceId] = clientCSDecorator(
78
+ clients[instanceId] = clientCSDecorator(
84
79
  log,
85
80
  sdkClientFactory(objectAssign({}, params, {
86
81
  sdkReadinessManager: sharedSdkReadiness,
87
82
  storage: sharedStorage || storage,
88
83
  syncManager: sharedSyncManager,
89
- signalListener: undefined, // only the main client "destroy" method stops the signal listener
90
84
  }), true) as SplitIO.IClient,
91
85
  validKey,
92
86
  validTrafficType
@@ -99,6 +93,6 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
99
93
  log.debug(RETRIEVE_CLIENT_EXISTING);
100
94
  }
101
95
 
102
- return clientInstances[instanceId];
96
+ return clients[instanceId] as SplitIO.ICsClient;
103
97
  };
104
98
  }
@@ -3,7 +3,7 @@ import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
3
3
  import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
4
4
  import { eventTrackerFactory } from '../trackers/eventTracker';
5
5
  import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
6
- import { SplitIO } from '../types';
6
+ import { IBasicClient, SplitIO } from '../types';
7
7
  import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
8
8
  import { createLoggerAPI } from '../logger/sdkLogger';
9
9
  import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
@@ -32,7 +32,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
32
32
  // We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
33
33
  validateAndTrackApiKey(log, settings.core.authorizationKey);
34
34
 
35
- const sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
35
+ const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
36
36
  const readiness = sdkReadinessManager.readinessManager;
37
37
 
38
38
  const storage = storageFactory({
@@ -48,7 +48,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
48
48
  },
49
49
  });
50
50
  // @TODO add support for dataloader: `if (params.dataLoader) params.dataLoader(storage);`
51
-
51
+ const clients: Record<string, IBasicClient> = {};
52
52
  const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
53
53
  const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
54
54
 
@@ -73,7 +73,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
73
73
  // splitApi is used by SyncManager and Browser signal listener
74
74
  const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
75
75
 
76
- const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
76
+ const ctx: ISdkFactoryContext = { clients, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform };
77
77
 
78
78
  const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
79
79
  ctx.syncManager = syncManager;
@@ -105,5 +105,9 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
105
105
  Logger: createLoggerAPI(log),
106
106
 
107
107
  settings,
108
+
109
+ destroy() {
110
+ return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => {});
111
+ }
108
112
  }, extraProps && extraProps(ctx));
109
113
  }
@@ -8,7 +8,7 @@ import { IStorageAsync, IStorageSync, IStorageFactoryParams } from '../storages/
8
8
  import { ISyncManager } from '../sync/types';
9
9
  import { IImpressionObserver } from '../trackers/impressionObserver/types';
10
10
  import { IImpressionsTracker, IEventTracker, ITelemetryTracker, IFilterAdapter, IUniqueKeysTracker } from '../trackers/types';
11
- import { SplitIO, ISettings, IEventEmitter } from '../types';
11
+ import { SplitIO, ISettings, IEventEmitter, IBasicClient } from '../types';
12
12
 
13
13
  /**
14
14
  * Environment related dependencies.
@@ -49,6 +49,7 @@ export interface ISdkFactoryContext {
49
49
  signalListener?: ISignalListener
50
50
  splitApi?: ISplitApi
51
51
  syncManager?: ISyncManager,
52
+ clients: Record<string, IBasicClient>,
52
53
  }
53
54
 
54
55
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
@@ -4,7 +4,7 @@ import { splitHttpClientFactory } from './splitHttpClient';
4
4
  import { ISplitApi } from './types';
5
5
  import { objectAssign } from '../utils/lang/objectAssign';
6
6
  import { ITelemetryTracker } from '../trackers/types';
7
- import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MY_SEGMENT } from '../utils/constants';
7
+ import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MEMBERSHIPS } from '../utils/constants';
8
8
  import { ERROR_TOO_MANY_SETS } from '../logger/constants';
9
9
 
10
10
  const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
@@ -22,7 +22,7 @@ function userKeyToQueryParam(userKey: string) {
22
22
  */
23
23
  export function splitApiFactory(
24
24
  settings: ISettings,
25
- platform: IPlatform,
25
+ platform: Pick<IPlatform, 'getOptions' | 'getFetch'>,
26
26
  telemetryTracker: ITelemetryTracker
27
27
  ): ISplitApi {
28
28
 
@@ -67,15 +67,15 @@ export function splitApiFactory(
67
67
  return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT));
68
68
  },
69
69
 
70
- fetchMySegments(userMatchingKey: string, noCache?: boolean) {
70
+ fetchMemberships(userMatchingKey: string, noCache?: boolean, till?: number) {
71
71
  /**
72
72
  * URI encoding of user keys in order to:
73
- * - avoid 400 responses (due to URI malformed). E.g.: '/api/mySegments/%'
74
- * - avoid 404 responses. E.g.: '/api/mySegments/foo/bar'
73
+ * - avoid 400 responses (due to URI malformed). E.g.: '/api/memberships/%'
74
+ * - avoid 404 responses. E.g.: '/api/memberships/foo/bar'
75
75
  * - match user keys with special characters. E.g.: 'foo%bar', 'foo/bar'
76
76
  */
77
- const url = `${urls.sdk}/mySegments/${encodeURIComponent(userMatchingKey)}`;
78
- return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(MY_SEGMENT));
77
+ const url = `${urls.sdk}/memberships/${encodeURIComponent(userMatchingKey)}${till ? '?till=' + till : ''}`;
78
+ return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(MEMBERSHIPS));
79
79
  },
80
80
 
81
81
  /**
@@ -13,7 +13,7 @@ const messageNoFetch = 'Global fetch API is not available.';
13
13
  * @param settings SDK settings, used to access authorizationKey, logger instance and metadata (SDK version, ip and hostname) to set additional headers
14
14
  * @param platform object containing environment-specific dependencies
15
15
  */
16
- export function splitHttpClientFactory(settings: ISettings, { getOptions, getFetch }: IPlatform): ISplitHttpClient {
16
+ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFetch }: Pick<IPlatform, 'getOptions' | 'getFetch'>): ISplitHttpClient {
17
17
 
18
18
  const { log, core: { authorizationKey }, version, runtime: { ip, hostname } } = settings;
19
19
  const options = getOptions && getOptions(settings);
@@ -39,7 +39,7 @@ export type IFetchSplitChanges = (since: number, noCache?: boolean, till?: numbe
39
39
 
40
40
  export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>
41
41
 
42
- export type IFetchMySegments = (userMatchingKey: string, noCache?: boolean) => Promise<IResponse>
42
+ export type IFetchMemberships = (userMatchingKey: string, noCache?: boolean, till?: number) => Promise<IResponse>
43
43
 
44
44
  export type IPostEventsBulk = (body: string, headers?: Record<string, string>) => Promise<IResponse>
45
45
 
@@ -61,7 +61,7 @@ export interface ISplitApi {
61
61
  fetchAuth: IFetchAuth
62
62
  fetchSplitChanges: IFetchSplitChanges
63
63
  fetchSegmentChanges: IFetchSegmentChanges
64
- fetchMySegments: IFetchMySegments
64
+ fetchMemberships: IFetchMemberships
65
65
  postEventsBulk: IPostEventsBulk
66
66
  postUniqueKeysBulkCs: IPostUniqueKeysBulkCs
67
67
  postUniqueKeysBulkSs: IPostUniqueKeysBulkSs
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  /* eslint-disable no-unused-vars */
3
+ import { IMySegmentsResponse } from '../dtos/types';
4
+ import { MySegmentsData } from '../sync/polling/types';
3
5
  import { ISegmentsCacheSync } from './types';
4
6
 
5
7
  /**
@@ -28,7 +30,9 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
28
30
  /**
29
31
  * clear the cache.
30
32
  */
31
- abstract clear(): void
33
+ clear() {
34
+ this.resetSegments({});
35
+ }
32
36
 
33
37
  /**
34
38
  * For server-side synchronizer: add the given list of segments to the cache, with an empty list of keys. The segments that already exist are not modified.
@@ -49,20 +53,57 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
49
53
  abstract getKeysCount(): number
50
54
 
51
55
  /**
52
- * For server-side synchronizer: set the change number of `name` segment.
53
- * For client-side synchronizer: the method is not used.
54
- */
55
- setChangeNumber(name: string, changeNumber: number): boolean { return true; }
56
-
57
- /**
58
- * For server-side synchronizer: get the change number of `name` segment.
59
- * For client-side synchronizer: the method is not used.
56
+ * For server-side synchronizer: change number of `name` segment.
57
+ * For client-side synchronizer: change number of mySegments.
60
58
  */
61
- getChangeNumber(name: string): number { return -1; }
59
+ abstract setChangeNumber(name?: string, changeNumber?: number): boolean | void
60
+ abstract getChangeNumber(name: string): number
62
61
 
63
62
  /**
64
63
  * For server-side synchronizer: the method is not used.
65
- * For client-side synchronizer: reset the cache with the given list of segments.
64
+ * For client-side synchronizer: it resets or updates the cache.
66
65
  */
67
- resetSegments(names: string[]): boolean { return true; }
66
+ resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
67
+ this.setChangeNumber(undefined, segmentsData.cn);
68
+
69
+ const { added, removed } = segmentsData as MySegmentsData;
70
+
71
+ if (added && removed) {
72
+ let isDiff = false;
73
+
74
+ added.forEach(segment => {
75
+ isDiff = this.addToSegment(segment) || isDiff;
76
+ });
77
+
78
+ removed.forEach(segment => {
79
+ isDiff = this.removeFromSegment(segment) || isDiff;
80
+ });
81
+
82
+ return isDiff;
83
+ }
84
+
85
+ const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
86
+ const storedSegmentKeys = this.getRegisteredSegments().sort();
87
+
88
+ // Extreme fast => everything is empty
89
+ if (!names.length && !storedSegmentKeys.length) return false;
90
+
91
+ let index = 0;
92
+
93
+ while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
94
+
95
+ // Quick path => no changes
96
+ if (index === names.length && index === storedSegmentKeys.length) return false;
97
+
98
+ // Slowest path => add and/or remove segments
99
+ for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
100
+ this.removeFromSegment(storedSegmentKeys[removeIndex]);
101
+ }
102
+
103
+ for (let addIndex = index; addIndex < names.length; addIndex++) {
104
+ this.addToSegment(names[addIndex]);
105
+ }
106
+
107
+ return true;
108
+ }
68
109
  }
@@ -2,7 +2,7 @@ import { ISplitsCacheSync } from './types';
2
2
  import { ISplit } from '../dtos/types';
3
3
  import { objectAssign } from '../utils/lang/objectAssign';
4
4
  import { ISet } from '../utils/lang/sets';
5
- import { IN_SEGMENT } from '../utils/constants';
5
+ import { IN_SEGMENT, IN_LARGE_SEGMENT } from '../utils/constants';
6
6
 
7
7
  /**
8
8
  * This class provides a skeletal implementation of the ISplitsCacheSync interface
@@ -32,7 +32,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
32
32
  return splits;
33
33
  }
34
34
 
35
- abstract setChangeNumber(changeNumber: number): boolean
35
+ abstract setChangeNumber(changeNumber: number): boolean | void
36
36
 
37
37
  abstract getChangeNumber(): number
38
38
 
@@ -94,7 +94,8 @@ export function usesSegments(split: ISplit) {
94
94
  const matchers = conditions[i].matcherGroup.matchers;
95
95
 
96
96
  for (let j = 0; j < matchers.length; j++) {
97
- if (matchers[j].matcherType === IN_SEGMENT) return true;
97
+ const matcher = matchers[j].matcherType;
98
+ if (matcher === IN_SEGMENT || matcher === IN_LARGE_SEGMENT) return true;
98
99
  }
99
100
  }
100
101
 
@@ -1,7 +1,14 @@
1
1
  import { startsWith } from '../utils/lang';
2
2
  import { KeyBuilder } from './KeyBuilder';
3
3
 
4
- export class KeyBuilderCS extends KeyBuilder {
4
+ export interface MySegmentsKeyBuilder {
5
+ buildSegmentNameKey(segmentName: string): string;
6
+ extractSegmentName(builtSegmentKeyName: string): string | undefined;
7
+ extractOldSegmentKey(builtSegmentKeyName: string): string | undefined;
8
+ buildTillKey(): string;
9
+ }
10
+
11
+ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
5
12
 
6
13
  protected readonly regexSplitsCacheKey: RegExp;
7
14
  protected readonly matchingKey: string;
@@ -26,10 +33,6 @@ export class KeyBuilderCS extends KeyBuilder {
26
33
  return builtSegmentKeyName.substr(prefix.length);
27
34
  }
28
35
 
29
- // @BREAKING: The key used to start with the matching key instead of the prefix, this was changed on version 10.17.3
30
- buildOldSegmentNameKey(segmentName: string) {
31
- return `${this.matchingKey}.${this.prefix}.segment.${segmentName}`;
32
- }
33
36
  // @BREAKING: The key used to start with the matching key instead of the prefix, this was changed on version 10.17.3
34
37
  extractOldSegmentKey(builtSegmentKeyName: string) {
35
38
  const prefix = `${this.matchingKey}.${this.prefix}.segment.`;
@@ -45,4 +48,30 @@ export class KeyBuilderCS extends KeyBuilder {
45
48
  isSplitsCacheKey(key: string) {
46
49
  return this.regexSplitsCacheKey.test(key);
47
50
  }
51
+
52
+ buildTillKey() {
53
+ return `${this.prefix}.${this.matchingKey}.segments.till`;
54
+ }
55
+ }
56
+
57
+ export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {
58
+ return {
59
+ buildSegmentNameKey(segmentName: string) {
60
+ return `${prefix}.${matchingKey}.largeSegment.${segmentName}`;
61
+ },
62
+
63
+ extractSegmentName(builtSegmentKeyName: string) {
64
+ const p = `${prefix}.${matchingKey}.largeSegment.`;
65
+
66
+ if (startsWith(builtSegmentKeyName, p)) return builtSegmentKeyName.substr(p.length);
67
+ },
68
+
69
+ extractOldSegmentKey() {
70
+ return undefined;
71
+ },
72
+
73
+ buildTillKey() {
74
+ return `${prefix}.${matchingKey}.largeSegments.till`;
75
+ }
76
+ };
48
77
  }
@@ -50,6 +50,6 @@ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
50
50
  return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
51
51
  });
52
52
  }
53
- storage.segments.resetSegments(mySegmentsData);
53
+ storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
54
54
  };
55
55
  }
@@ -1,36 +1,26 @@
1
1
  import { ILogger } from '../../logger/types';
2
+ import { isNaNNumber } from '../../utils/lang';
2
3
  import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
3
- import { KeyBuilderCS } from '../KeyBuilderCS';
4
+ import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
4
5
  import { LOG_PREFIX, DEFINED } from './constants';
5
6
 
6
7
  export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
7
8
 
8
- private readonly keys: KeyBuilderCS;
9
+ private readonly keys: MySegmentsKeyBuilder;
9
10
  private readonly log: ILogger;
10
11
 
11
- constructor(log: ILogger, keys: KeyBuilderCS) {
12
+ constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
12
13
  super();
13
14
  this.log = log;
14
15
  this.keys = keys;
15
16
  // There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
16
17
  }
17
18
 
18
- /**
19
- * Removes list of segments from localStorage
20
- * @NOTE this method is not being used at the moment.
21
- */
22
- clear() {
23
- this.log.info(LOG_PREFIX + 'Flushing MySegments data from localStorage');
24
-
25
- // We cannot simply call `localStorage.clear()` since that implies removing user items from the storage
26
- // We could optimize next sentence, since it implies iterating over all localStorage items
27
- this.resetSegments([]);
28
- }
29
-
30
19
  addToSegment(name: string): boolean {
31
20
  const segmentKey = this.keys.buildSegmentNameKey(name);
32
21
 
33
22
  try {
23
+ if (localStorage.getItem(segmentKey) === DEFINED) return false;
34
24
  localStorage.setItem(segmentKey, DEFINED);
35
25
  return true;
36
26
  } catch (e) {
@@ -43,6 +33,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
43
33
  const segmentKey = this.keys.buildSegmentNameKey(name);
44
34
 
45
35
  try {
36
+ if (localStorage.getItem(segmentKey) !== DEFINED) return false;
46
37
  localStorage.removeItem(segmentKey);
47
38
  return true;
48
39
  } catch (e) {
@@ -55,31 +46,22 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
55
46
  return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
56
47
  }
57
48
 
58
- /**
59
- * Reset (update) the cached list of segments with the given list, removing and adding segments if necessary.
60
- *
61
- * @param {string[]} segmentNames list of segment names
62
- * @returns boolean indicating if the cache was updated (i.e., given list was different from the cached one)
63
- */
64
- resetSegments(names: string[]): boolean {
65
- let isDiff = false;
66
- let index;
67
-
49
+ getRegisteredSegments(): string[] {
68
50
  // Scan current values from localStorage
69
- const storedSegmentNames = Object.keys(localStorage).reduce((accum, key) => {
51
+ return Object.keys(localStorage).reduce((accum, key) => {
70
52
  let segmentName = this.keys.extractSegmentName(key);
71
53
 
72
54
  if (segmentName) {
73
55
  accum.push(segmentName);
74
56
  } else {
75
- // @TODO @BREAKING: This is only to clean up "old" keys. Remove this whole else code block and reuse `getRegisteredSegments` method.
57
+ // @TODO @BREAKING: This is only to clean up "old" keys. Remove this whole else code block
76
58
  segmentName = this.keys.extractOldSegmentKey(key);
77
59
 
78
60
  if (segmentName) { // this was an old segment key, let's clean up.
79
61
  const newSegmentKey = this.keys.buildSegmentNameKey(segmentName);
80
62
  try {
81
63
  // If the new format key is not there, create it.
82
- if (!localStorage.getItem(newSegmentKey) && names.indexOf(segmentName) > -1) {
64
+ if (!localStorage.getItem(newSegmentKey)) {
83
65
  localStorage.setItem(newSegmentKey, DEFINED);
84
66
  // we are migrating a segment, let's track it.
85
67
  accum.push(segmentName);
@@ -93,44 +75,32 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
93
75
 
94
76
  return accum;
95
77
  }, [] as string[]);
78
+ }
96
79
 
97
- // Extreme fast => everything is empty
98
- if (names.length === 0 && storedSegmentNames.length === names.length)
99
- return isDiff;
80
+ getKeysCount() {
81
+ return 1;
82
+ }
100
83
 
101
- // Quick path
102
- if (storedSegmentNames.length !== names.length) {
103
- isDiff = true;
84
+ setChangeNumber(name?: string, changeNumber?: number) {
85
+ try {
86
+ if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
87
+ else localStorage.removeItem(this.keys.buildTillKey());
88
+ } catch (e) {
89
+ this.log.error(e);
90
+ }
91
+ }
104
92
 
105
- storedSegmentNames.forEach(name => this.removeFromSegment(name));
106
- names.forEach(name => this.addToSegment(name));
107
- } else {
108
- // Slowest path => we need to find at least 1 difference because
109
- for (index = 0; index < names.length && storedSegmentNames.indexOf(names[index]) !== -1; index++) {
110
- // TODO: why empty statement?
111
- }
93
+ getChangeNumber() {
94
+ const n = -1;
95
+ let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
112
96
 
113
- if (index < names.length) {
114
- isDiff = true;
97
+ if (value !== null) {
98
+ value = parseInt(value, 10);
115
99
 
116
- storedSegmentNames.forEach(name => this.removeFromSegment(name));
117
- names.forEach(name => this.addToSegment(name));
118
- }
100
+ return isNaNNumber(value) ? n : value;
119
101
  }
120
102
 
121
- return isDiff;
122
- }
123
-
124
- getRegisteredSegments(): string[] {
125
- return Object.keys(localStorage).reduce<string[]>((accum, key) => {
126
- const segmentName = this.keys.extractSegmentName(key);
127
- if (segmentName) accum.push(segmentName);
128
- return accum;
129
- }, []);
130
- }
131
-
132
- getKeysCount() {
133
- return 1;
103
+ return n;
134
104
  }
135
105
 
136
106
  }
@@ -3,7 +3,7 @@ import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCache
3
3
  import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
4
4
  import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types';
5
5
  import { validatePrefix } from '../KeyBuilder';
6
- import { KeyBuilderCS } from '../KeyBuilderCS';
6
+ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
7
7
  import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
8
8
  import { SplitsCacheInLocal } from './SplitsCacheInLocal';
9
9
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
@@ -38,15 +38,17 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
38
38
 
39
39
  const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
40
40
  const matchingKey = getMatching(settings.core.key);
41
- const keys = new KeyBuilderCS(prefix, matchingKey as string);
41
+ const keys = new KeyBuilderCS(prefix, matchingKey);
42
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
43
43
 
44
44
  const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
45
45
  const segments = new MySegmentsCacheInLocal(log, keys);
46
+ const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
46
47
 
47
48
  return {
48
49
  splits,
49
50
  segments,
51
+ largeSegments,
50
52
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
51
53
  impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
52
54
  events: new EventsCacheInMemory(eventsQueueSize),
@@ -56,6 +58,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
56
58
  destroy() {
57
59
  this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
58
60
  this.segments = new MySegmentsCacheInMemory();
61
+ this.largeSegments = new MySegmentsCacheInMemory();
59
62
  this.impressions.clear();
60
63
  this.impressionCounts && this.impressionCounts.clear();
61
64
  this.events.clear();
@@ -64,11 +67,11 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
64
67
 
65
68
  // When using shared instanciation with MEMORY we reuse everything but segments (they are customer per key).
66
69
  shared(matchingKey: string) {
67
- const childKeysBuilder = new KeyBuilderCS(prefix, matchingKey);
68
70
 
69
71
  return {
70
72
  splits: this.splits,
71
- segments: new MySegmentsCacheInLocal(log, childKeysBuilder),
73
+ segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
74
+ largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
72
75
  impressions: this.impressions,
73
76
  impressionCounts: this.impressionCounts,
74
77
  events: this.events,
@@ -77,6 +80,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
77
80
  destroy() {
78
81
  this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
79
82
  this.segments = new MySegmentsCacheInMemory();
83
+ this.largeSegments = new MySegmentsCacheInMemory();
80
84
  }
81
85
  };
82
86
  },
@@ -18,10 +18,12 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
18
18
 
19
19
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
20
20
  const segments = new MySegmentsCacheInMemory();
21
+ const largeSegments = new MySegmentsCacheInMemory();
21
22
 
22
23
  const storage = {
23
24
  splits,
24
25
  segments,
26
+ largeSegments,
25
27
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
28
  impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
27
29
  events: new EventsCacheInMemory(eventsQueueSize),
@@ -32,6 +34,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
32
34
  destroy() {
33
35
  this.splits.clear();
34
36
  this.segments.clear();
37
+ this.largeSegments.clear();
35
38
  this.impressions.clear();
36
39
  this.impressionCounts && this.impressionCounts.clear();
37
40
  this.events.clear();
@@ -43,6 +46,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
43
46
  return {
44
47
  splits: this.splits,
45
48
  segments: new MySegmentsCacheInMemory(),
49
+ largeSegments: new MySegmentsCacheInMemory(),
46
50
  impressions: this.impressions,
47
51
  impressionCounts: this.impressionCounts,
48
52
  events: this.events,
@@ -52,6 +56,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
52
56
  destroy() {
53
57
  this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
54
58
  this.segments.clear();
59
+ this.largeSegments.clear();
55
60
  }
56
61
  };
57
62
  },