@splitsoftware/splitio-commons 1.17.1-rc.3 → 1.17.1-rc.4

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 (107) hide show
  1. package/CHANGES.txt +4 -3
  2. package/cjs/readiness/readinessManager.js +13 -4
  3. package/cjs/sdkClient/sdkClientMethodCS.js +3 -7
  4. package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -7
  5. package/cjs/sdkFactory/index.js +8 -13
  6. package/cjs/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +15 -17
  7. package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
  8. package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
  9. package/cjs/storages/dataLoader.js +32 -64
  10. package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  11. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
  12. package/cjs/storages/inLocalStorage/index.js +1 -6
  13. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -17
  14. package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  15. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  16. package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -1
  17. package/cjs/storages/inRedis/RedisAdapter.js +1 -1
  18. package/cjs/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  19. package/cjs/storages/pluggable/SegmentsCachePluggable.js +11 -32
  20. package/cjs/storages/pluggable/index.js +32 -37
  21. package/cjs/sync/offline/syncManagerOffline.js +18 -11
  22. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +7 -2
  23. package/cjs/sync/polling/pollingManagerSS.js +3 -3
  24. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  25. package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
  26. package/cjs/sync/syncManagerOnline.js +20 -21
  27. package/cjs/trackers/eventTracker.js +1 -1
  28. package/cjs/trackers/impressionsTracker.js +1 -1
  29. package/cjs/utils/settingsValidation/storage/storageCS.js +12 -1
  30. package/esm/readiness/readinessManager.js +13 -4
  31. package/esm/sdkClient/sdkClientMethodCS.js +3 -7
  32. package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -7
  33. package/esm/sdkFactory/index.js +9 -14
  34. package/esm/storages/{AbstractSegmentsCacheSync.js → AbstractMySegmentsCacheSync.js} +14 -16
  35. package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
  36. package/esm/storages/AbstractSplitsCacheSync.js +7 -0
  37. package/esm/storages/dataLoader.js +30 -61
  38. package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +5 -5
  39. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -1
  40. package/esm/storages/inLocalStorage/index.js +2 -7
  41. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -17
  42. package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -5
  43. package/esm/storages/inMemory/SegmentsCacheInMemory.js +13 -27
  44. package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -1
  45. package/esm/storages/inRedis/RedisAdapter.js +1 -1
  46. package/esm/storages/inRedis/SegmentsCacheInRedis.js +13 -19
  47. package/esm/storages/pluggable/SegmentsCachePluggable.js +11 -32
  48. package/esm/storages/pluggable/index.js +32 -37
  49. package/esm/sync/offline/syncManagerOffline.js +18 -11
  50. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +8 -3
  51. package/esm/sync/polling/pollingManagerSS.js +3 -3
  52. package/esm/sync/polling/updaters/segmentChangesUpdater.js +12 -28
  53. package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
  54. package/esm/sync/syncManagerOnline.js +20 -21
  55. package/esm/trackers/eventTracker.js +1 -1
  56. package/esm/trackers/impressionsTracker.js +1 -1
  57. package/esm/utils/settingsValidation/storage/storageCS.js +10 -0
  58. package/package.json +1 -1
  59. package/src/readiness/readinessManager.ts +11 -4
  60. package/src/readiness/types.ts +2 -0
  61. package/src/sdkClient/sdkClientMethodCS.ts +1 -6
  62. package/src/sdkClient/sdkClientMethodCSWithTT.ts +1 -6
  63. package/src/sdkFactory/index.ts +9 -15
  64. package/src/sdkFactory/types.ts +1 -2
  65. package/src/storages/{AbstractSegmentsCacheSync.ts → AbstractMySegmentsCacheSync.ts} +13 -28
  66. package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
  67. package/src/storages/AbstractSplitsCacheSync.ts +8 -0
  68. package/src/storages/dataLoader.ts +32 -62
  69. package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +5 -5
  70. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -1
  71. package/src/storages/inLocalStorage/index.ts +2 -8
  72. package/src/storages/inMemory/InMemoryStorageCS.ts +5 -20
  73. package/src/storages/inMemory/MySegmentsCacheInMemory.ts +5 -5
  74. package/src/storages/inMemory/SegmentsCacheInMemory.ts +12 -26
  75. package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -1
  76. package/src/storages/inRedis/RedisAdapter.ts +1 -1
  77. package/src/storages/inRedis/SegmentsCacheInRedis.ts +13 -22
  78. package/src/storages/pluggable/SegmentsCachePluggable.ts +11 -35
  79. package/src/storages/pluggable/index.ts +33 -38
  80. package/src/storages/types.ts +9 -11
  81. package/src/sync/offline/syncManagerOffline.ts +21 -13
  82. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +7 -3
  83. package/src/sync/polling/pollingManagerSS.ts +2 -3
  84. package/src/sync/polling/updaters/segmentChangesUpdater.ts +13 -29
  85. package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -3
  86. package/src/sync/syncManagerOnline.ts +17 -17
  87. package/src/sync/types.ts +1 -1
  88. package/src/trackers/eventTracker.ts +1 -1
  89. package/src/trackers/impressionsTracker.ts +1 -1
  90. package/src/types.ts +8 -9
  91. package/src/utils/settingsValidation/storage/storageCS.ts +13 -0
  92. package/types/readiness/types.d.ts +2 -0
  93. package/types/sdkFactory/types.d.ts +1 -2
  94. package/types/storages/AbstractSplitsCacheAsync.d.ts +5 -0
  95. package/types/storages/AbstractSplitsCacheSync.d.ts +5 -0
  96. package/types/storages/dataLoader.d.ts +6 -17
  97. package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +5 -5
  98. package/types/storages/inLocalStorage/SplitsCacheInLocal.d.ts +6 -0
  99. package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +5 -5
  100. package/types/storages/inMemory/SegmentsCacheInMemory.d.ts +5 -7
  101. package/types/storages/inMemory/SplitsCacheInMemory.d.ts +0 -1
  102. package/types/storages/inRedis/SegmentsCacheInRedis.d.ts +6 -3
  103. package/types/storages/pluggable/SegmentsCachePluggable.d.ts +4 -16
  104. package/types/storages/types.d.ts +7 -11
  105. package/types/sync/types.d.ts +1 -1
  106. package/types/types.d.ts +8 -8
  107. package/types/utils/settingsValidation/storage/storageCS.d.ts +5 -0
@@ -50,7 +50,6 @@ export interface ISdkFactoryContext {
50
50
  splitApi?: ISplitApi
51
51
  syncManager?: ISyncManager,
52
52
  clients: Record<string, IBasicClient>,
53
- whenInit(cb: () => void): void
54
53
  }
55
54
 
56
55
  export interface ISdkFactoryContextSync extends ISdkFactoryContext {
@@ -70,7 +69,7 @@ export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
70
69
  */
71
70
  export interface ISdkFactoryParams {
72
71
  // If true, the `sdkFactory` is pure (no side effects), and the SDK instance includes a `init` method to run initialization side effects
73
- isPure?: boolean,
72
+ lazyInit?: boolean,
74
73
 
75
74
  // The settings must be already validated
76
75
  settings: ISettings,
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- /* eslint-disable no-unused-vars */
3
1
  import { IMySegmentsResponse } from '../dtos/types';
4
2
  import { MySegmentsData } from '../sync/polling/types';
5
3
  import { ISegmentsCacheSync } from './types';
@@ -8,18 +6,11 @@ import { ISegmentsCacheSync } from './types';
8
6
  * This class provides a skeletal implementation of the ISegmentsCacheSync interface
9
7
  * to minimize the effort required to implement this interface.
10
8
  */
11
- export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
12
- /**
13
- * For server-side synchronizer: add `segmentKeys` list of keys to `name` segment.
14
- * For client-side synchronizer: add `name` segment to the cache. `segmentKeys` is undefined.
15
- */
16
- abstract addToSegment(name: string, segmentKeys?: string[]): boolean
9
+ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync {
17
10
 
18
- /**
19
- * For server-side synchronizer: remove `segmentKeys` list of keys from `name` segment.
20
- * For client-side synchronizer: remove `name` segment from the cache. `segmentKeys` is undefined.
21
- */
22
- abstract removeFromSegment(name: string, segmentKeys?: string[]): boolean
11
+ protected abstract addSegment(name: string): boolean
12
+ protected abstract removeSegment(name: string): boolean
13
+ protected abstract setChangeNumber(changeNumber?: number): boolean | void
23
14
 
24
15
  /**
25
16
  * For server-side synchronizer: check if `key` is in `name` segment.
@@ -34,11 +25,10 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
34
25
  this.resetSegments({});
35
26
  }
36
27
 
37
- /**
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.
39
- * For client-side synchronizer: the method is not used.
40
- */
41
- registerSegments(names: string[]): boolean { return false; }
28
+
29
+ // No-op. Not used in client-side.
30
+ registerSegments(): boolean { return false; }
31
+ update() { return false; }
42
32
 
43
33
  /**
44
34
  * For server-side synchronizer: get the list of segments to fetch changes.
@@ -52,11 +42,6 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
52
42
  */
53
43
  abstract getKeysCount(): number
54
44
 
55
- /**
56
- * For server-side synchronizer: change number of `name` segment.
57
- * For client-side synchronizer: change number of mySegments.
58
- */
59
- abstract setChangeNumber(name?: string, changeNumber?: number): boolean | void
60
45
  abstract getChangeNumber(name: string): number
61
46
 
62
47
  /**
@@ -64,7 +49,7 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
64
49
  * For client-side synchronizer: it resets or updates the cache.
65
50
  */
66
51
  resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
67
- this.setChangeNumber(undefined, segmentsData.cn);
52
+ this.setChangeNumber(segmentsData.cn);
68
53
 
69
54
  const { added, removed } = segmentsData as MySegmentsData;
70
55
 
@@ -72,11 +57,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
72
57
  let isDiff = false;
73
58
 
74
59
  added.forEach(segment => {
75
- isDiff = this.addToSegment(segment) || isDiff;
60
+ isDiff = this.addSegment(segment) || isDiff;
76
61
  });
77
62
 
78
63
  removed.forEach(segment => {
79
- isDiff = this.removeFromSegment(segment) || isDiff;
64
+ isDiff = this.removeSegment(segment) || isDiff;
80
65
  });
81
66
 
82
67
  return isDiff;
@@ -97,11 +82,11 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
97
82
 
98
83
  // Slowest path => add and/or remove segments
99
84
  for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
100
- this.removeFromSegment(storedSegmentKeys[removeIndex]);
85
+ this.removeSegment(storedSegmentKeys[removeIndex]);
101
86
  }
102
87
 
103
88
  for (let addIndex = index; addIndex < names.length; addIndex++) {
104
- this.addToSegment(names[addIndex]);
89
+ this.addSegment(names[addIndex]);
105
90
  }
106
91
 
107
92
  return true;
@@ -28,6 +28,14 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
28
28
  return Promise.resolve(true);
29
29
  }
30
30
 
31
+ /**
32
+ * Check if the splits information is already stored in cache.
33
+ * Noop, just keeping the interface. This is used by client-side implementations only.
34
+ */
35
+ checkCache(): Promise<boolean> {
36
+ return Promise.resolve(false);
37
+ }
38
+
31
39
  /**
32
40
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
33
41
  * Used for SPLIT_KILL push notifications.
@@ -48,6 +48,14 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
48
48
 
49
49
  abstract clear(): void
50
50
 
51
+ /**
52
+ * Check if the splits information is already stored in cache. This data can be preloaded.
53
+ * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
54
+ */
55
+ checkCache(): boolean {
56
+ return false;
57
+ }
58
+
51
59
  /**
52
60
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
53
61
  * Used for SPLIT_KILL push notifications.
@@ -1,85 +1,55 @@
1
1
  import { SplitIO } from '../types';
2
- import { ISegmentsCacheSync, ISplitsCacheSync, IStorageSync } from './types';
3
- import { setToArray, ISet } from '../utils/lang/sets';
4
- import { getMatching } from '../utils/key';
2
+ import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
3
+ import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
5
4
 
6
5
  /**
7
- * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
8
- * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
6
+ * Factory of client-side storage loader
9
7
  *
10
- * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader and extended with a `mySegmentsData` property.
11
- * @param storage object containing `splits` and `segments` cache (client-side variant)
12
- * @param userKey user key (matching key) of the provided MySegmentsCache
13
- *
14
- * @TODO extend to load largeSegments
15
- * @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
16
- * @TODO add logs, and input validation in this module, in favor of size reduction.
17
- * @TODO unit tests
8
+ * @param preloadedData validated data following the format proposed in https://github.com/godaddy/split-javascript-data-loader
9
+ * and extended with a `mySegmentsData` property.
10
+ * @returns function to preload the storage
18
11
  */
19
- export function loadData(preloadedData: SplitIO.PreloadedData, storage: { splits?: ISplitsCacheSync, segments: ISegmentsCacheSync, largeSegments?: ISegmentsCacheSync }, matchingKey?: string) {
20
- // Do not load data if current preloadedData is empty
21
- if (Object.keys(preloadedData).length === 0) return;
22
-
23
- const { segmentsData = {}, since = -1, splitsData = [] } = preloadedData;
12
+ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoader {
13
+
14
+ /**
15
+ * Storage-agnostic adaptation of `loadDataIntoLocalStorage` function
16
+ * (https://github.com/godaddy/split-javascript-data-loader/blob/master/src/load-data.js)
17
+ *
18
+ * @param storage object containing `splits` and `segments` cache (client-side variant)
19
+ * @param userId user key string of the provided MySegmentsCache
20
+ *
21
+ * @TODO extend to support SegmentsCache (server-side variant) by making `userId` optional and adding the corresponding logic.
22
+ * @TODO extend to load data on shared mySegments storages. Be specific when emitting SDK_READY_FROM_CACHE on shared clients. Maybe the serializer should provide the `useSegments` flag.
23
+ */
24
+ return function loadData(storage: { splits: ISplitsCacheSync, segments: ISegmentsCacheSync }, userId: string) {
25
+ // Do not load data if current preloadedData is empty
26
+ if (Object.keys(preloadedData).length === 0) return;
27
+
28
+ const { lastUpdated = -1, segmentsData = {}, since = -1, splitsData = {} } = preloadedData;
24
29
 
25
- if (storage.splits) {
26
30
  const storedSince = storage.splits.getChangeNumber();
31
+ const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
27
32
 
28
- // Do not load data if current data is more recent
29
- if (storedSince > since) return;
33
+ // Do not load data if current localStorage data is more recent,
34
+ // or if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
35
+ if (storedSince > since || lastUpdated < expirationTimestamp) return;
30
36
 
31
37
  // cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
32
38
  storage.splits.clear();
33
39
  storage.splits.setChangeNumber(since);
34
40
 
35
41
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
36
- storage.splits.addSplits(splitsData.map(split => ([split.name, split])));
37
- }
42
+ storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
38
43
 
39
- if (matchingKey) { // add mySegments data (client-side)
40
- let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[matchingKey];
44
+ // add mySegments data
45
+ let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
41
46
  if (!mySegmentsData) {
42
47
  // segmentsData in an object where the property is the segment name and the pertaining value is a stringified object that contains the `added` array of userIds
43
48
  mySegmentsData = Object.keys(segmentsData).filter(segmentName => {
44
- const matchingKeys = segmentsData[segmentName];
45
- return matchingKeys.indexOf(matchingKey) > -1;
49
+ const userIds = JSON.parse(segmentsData[segmentName]).added;
50
+ return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
46
51
  });
47
52
  }
48
53
  storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
49
- } else { // add segments data (server-side)
50
- Object.keys(segmentsData).filter(segmentName => {
51
- const matchingKeys = segmentsData[segmentName];
52
- storage.segments.addToSegment(segmentName, matchingKeys);
53
- });
54
- }
55
- }
56
-
57
- export function getSnapshot(storage: IStorageSync, userKeys?: SplitIO.SplitKey[]): SplitIO.PreloadedData {
58
- return {
59
- // lastUpdated: Date.now(),
60
- since: storage.splits.getChangeNumber(),
61
- splitsData: storage.splits.getAll(),
62
- segmentsData: userKeys ?
63
- undefined : // @ts-ignore accessing private prop
64
- Object.keys(storage.segments.segmentCache).reduce((prev, cur) => { // @ts-ignore accessing private prop
65
- prev[cur] = setToArray(storage.segments.segmentCache[cur] as ISet<string>);
66
- return prev;
67
- }, {}),
68
- mySegmentsData: userKeys ?
69
- userKeys.reduce<Record<string, string[]>>((prev, userKey) => {
70
- prev[getMatching(userKey)] = storage.shared ?
71
- // Client-side segments
72
- // @ts-ignore accessing private prop
73
- Object.keys(storage.shared(userKey).segments.segmentCache) :
74
- // Server-side segments
75
- // @ts-ignore accessing private prop
76
- Object.keys(storage.segments.segmentCache).reduce<string[]>((prev, segmentName) => { // @ts-ignore accessing private prop
77
- return storage.segments.segmentCache[segmentName].has(userKey) ?
78
- prev.concat(segmentName) :
79
- prev;
80
- }, []);
81
- return prev;
82
- }, {}) :
83
- undefined
84
54
  };
85
55
  }
@@ -1,10 +1,10 @@
1
1
  import { ILogger } from '../../logger/types';
2
2
  import { isNaNNumber } from '../../utils/lang';
3
- import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
3
+ import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
4
4
  import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
5
5
  import { LOG_PREFIX, DEFINED } from './constants';
6
6
 
7
- export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
7
+ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
8
8
 
9
9
  private readonly keys: MySegmentsKeyBuilder;
10
10
  private readonly log: ILogger;
@@ -16,7 +16,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
16
16
  // There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
17
17
  }
18
18
 
19
- addToSegment(name: string): boolean {
19
+ protected addSegment(name: string): boolean {
20
20
  const segmentKey = this.keys.buildSegmentNameKey(name);
21
21
 
22
22
  try {
@@ -29,7 +29,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
29
29
  }
30
30
  }
31
31
 
32
- removeFromSegment(name: string): boolean {
32
+ protected removeSegment(name: string): boolean {
33
33
  const segmentKey = this.keys.buildSegmentNameKey(name);
34
34
 
35
35
  try {
@@ -81,7 +81,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
81
81
  return 1;
82
82
  }
83
83
 
84
- setChangeNumber(name?: string, changeNumber?: number) {
84
+ protected setChangeNumber(changeNumber?: number) {
85
85
  try {
86
86
  if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
87
87
  else localStorage.removeItem(this.keys.buildTillKey());
@@ -217,6 +217,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
217
217
  }
218
218
  }
219
219
 
220
+ /**
221
+ * Check if the splits information is already stored in browser LocalStorage.
222
+ * In this function we could add more code to check if the data is valid.
223
+ * @override
224
+ */
225
+ checkCache(): boolean {
226
+ return this.getChangeNumber() > -1;
227
+ }
228
+
220
229
  /**
221
230
  * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
222
231
  *
@@ -241,7 +250,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
241
250
  this.updateNewFilter = true;
242
251
 
243
252
  // if there is cache, clear it
244
- if (this.getChangeNumber() > -1) this.clear();
253
+ if (this.checkCache()) this.clear();
245
254
 
246
255
  } catch (e) {
247
256
  this.log.error(LOG_PREFIX + e);
@@ -12,7 +12,7 @@ import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
12
12
  import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
13
13
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
14
14
  import { LOG_PREFIX } from './constants';
15
- import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
15
+ import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
16
16
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
17
17
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
18
18
  import { getMatching } from '../../utils/key';
@@ -36,7 +36,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
36
36
  return InMemoryStorageCSFactory(params);
37
37
  }
38
38
 
39
- const { onReadyFromCacheCb, settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
39
+ const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
40
40
  const matchingKey = getMatching(settings.core.key);
41
41
  const keys = new KeyBuilderCS(prefix, matchingKey);
42
42
  const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
@@ -55,12 +55,6 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
55
55
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
56
56
  uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
57
57
 
58
- init() {
59
- if (settings.mode === LOCALHOST_MODE || splits.getChangeNumber() > -1) {
60
- Promise.resolve().then(onReadyFromCacheCb);
61
- }
62
- },
63
-
64
58
  destroy() {
65
59
  this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
66
60
  this.segments = new MySegmentsCacheInMemory();
@@ -7,8 +7,6 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
- import { getMatching } from '../../utils/key';
11
- import { loadData } from '../dataLoader';
12
10
 
13
11
  /**
14
12
  * InMemory storage factory for standalone client-side SplitFactory
@@ -16,7 +14,7 @@ import { loadData } from '../dataLoader';
16
14
  * @param params parameters required by EventsCacheSync
17
15
  */
18
16
  export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
19
- const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation }, preloadedData }, onReadyFromCacheCb } = params;
17
+ const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
20
18
 
21
19
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
22
20
  const segments = new MySegmentsCacheInMemory();
@@ -44,18 +42,11 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
44
42
  },
45
43
 
46
44
  // When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
47
- shared(matchingKey: string) {
48
- const segments = new MySegmentsCacheInMemory();
49
- const largeSegments = new MySegmentsCacheInMemory();
50
-
51
- if (preloadedData) {
52
- loadData(preloadedData, { segments, largeSegments }, matchingKey);
53
- }
54
-
45
+ shared() {
55
46
  return {
56
47
  splits: this.splits,
57
- segments,
58
- largeSegments,
48
+ segments: new MySegmentsCacheInMemory(),
49
+ largeSegments: new MySegmentsCacheInMemory(),
59
50
  impressions: this.impressions,
60
51
  impressionCounts: this.impressionCounts,
61
52
  events: this.events,
@@ -72,7 +63,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
72
63
  };
73
64
 
74
65
  // @TODO revisit storage logic in localhost mode
75
- // No tracking data in localhost mode to avoid memory leaks
66
+ // No tracking in localhost mode to avoid memory leaks: https://github.com/splitio/javascript-commons/issues/181
76
67
  if (params.settings.mode === LOCALHOST_MODE) {
77
68
  const noopTrack = () => true;
78
69
  storage.impressions.track = noopTrack;
@@ -81,12 +72,6 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
81
72
  if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
82
73
  }
83
74
 
84
-
85
- if (preloadedData) {
86
- loadData(preloadedData, storage, getMatching(params.settings.core.key));
87
- if (splits.getChangeNumber() > -1) onReadyFromCacheCb();
88
- }
89
-
90
75
  return storage;
91
76
  }
92
77
 
@@ -1,15 +1,15 @@
1
- import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
1
+ import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
2
2
 
3
3
  /**
4
4
  * Default MySegmentsCacheInMemory implementation that stores MySegments in memory.
5
5
  * Supported by all JS runtimes.
6
6
  */
7
- export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
7
+ export class MySegmentsCacheInMemory extends AbstractMySegmentsCacheSync {
8
8
 
9
9
  private segmentCache: Record<string, boolean> = {};
10
10
  private cn?: number;
11
11
 
12
- addToSegment(name: string): boolean {
12
+ protected addSegment(name: string): boolean {
13
13
  if (this.segmentCache[name]) return false;
14
14
 
15
15
  this.segmentCache[name] = true;
@@ -17,7 +17,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
17
17
  return true;
18
18
  }
19
19
 
20
- removeFromSegment(name: string): boolean {
20
+ protected removeSegment(name: string): boolean {
21
21
  if (!this.segmentCache[name]) return false;
22
22
 
23
23
  delete this.segmentCache[name];
@@ -30,7 +30,7 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
30
30
  }
31
31
 
32
32
 
33
- setChangeNumber(name?: string, changeNumber?: number) {
33
+ protected setChangeNumber(changeNumber?: number) {
34
34
  this.cn = changeNumber;
35
35
  }
36
36
 
@@ -1,36 +1,25 @@
1
- import { AbstractSegmentsCacheSync } from '../AbstractSegmentsCacheSync';
2
1
  import { ISet, _Set } from '../../utils/lang/sets';
3
2
  import { isIntegerNumber } from '../../utils/lang';
3
+ import { ISegmentsCacheSync } from '../types';
4
4
 
5
5
  /**
6
- * Default ISplitsCacheSync implementation that stores split definitions in memory.
7
- * Supported by all JS runtimes.
6
+ * Default ISplitsCacheSync implementation for server-side that stores segments definitions in memory.
8
7
  */
9
- export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync {
8
+ export class SegmentsCacheInMemory implements ISegmentsCacheSync {
10
9
 
11
10
  private segmentCache: Record<string, ISet<string>> = {};
12
11
  private segmentChangeNumber: Record<string, number> = {};
13
12
 
14
- addToSegment(name: string, segmentKeys: string[]): boolean {
15
- const values = this.segmentCache[name];
16
- const keySet = values ? values : new _Set<string>();
13
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
14
+ const keySet = this.segmentCache[name] || new _Set<string>();
17
15
 
18
- segmentKeys.forEach(k => keySet.add(k));
16
+ addedKeys.forEach(k => keySet.add(k));
17
+ removedKeys.forEach(k => keySet.delete(k));
19
18
 
20
19
  this.segmentCache[name] = keySet;
20
+ this.segmentChangeNumber[name] = changeNumber;
21
21
 
22
- return true;
23
- }
24
-
25
- removeFromSegment(name: string, segmentKeys: string[]): boolean {
26
- const values = this.segmentCache[name];
27
- const keySet = values ? values : new _Set<string>();
28
-
29
- segmentKeys.forEach(k => keySet.delete(k));
30
-
31
- this.segmentCache[name] = keySet;
32
-
33
- return true;
22
+ return addedKeys.length > 0 || removedKeys.length > 0;
34
23
  }
35
24
 
36
25
  isInSegment(name: string, key: string): boolean {
@@ -74,16 +63,13 @@ export class SegmentsCacheInMemory extends AbstractSegmentsCacheSync {
74
63
  }, 0);
75
64
  }
76
65
 
77
- setChangeNumber(name: string, changeNumber: number) {
78
- this.segmentChangeNumber[name] = changeNumber;
79
-
80
- return true;
81
- }
82
-
83
66
  getChangeNumber(name: string) {
84
67
  const value = this.segmentChangeNumber[name];
85
68
 
86
69
  return isIntegerNumber(value) ? value : -1;
87
70
  }
88
71
 
72
+ // No-op. Not used in server-side
73
+ resetSegments() { return false; }
74
+
89
75
  }
@@ -5,7 +5,6 @@ import { ISet, _Set } from '../../utils/lang/sets';
5
5
 
6
6
  /**
7
7
  * Default ISplitsCacheSync implementation that stores split definitions in memory.
8
- * Supported by all JS runtimes.
9
8
  */
10
9
  export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
11
10
 
@@ -20,7 +20,7 @@ const DEFAULT_OPTIONS = {
20
20
  const DEFAULT_LIBRARY_OPTIONS = {
21
21
  enableOfflineQueue: false,
22
22
  connectTimeout: DEFAULT_OPTIONS.connectionTimeout,
23
- lazyConnect: false // @TODO true to avoid side-effects on instantiation.
23
+ lazyConnect: false
24
24
  };
25
25
 
26
26
  interface IRedisCommand {
@@ -17,24 +17,21 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
17
17
  this.keys = keys;
18
18
  }
19
19
 
20
- addToSegment(name: string, segmentKeys: string[]) {
20
+ /**
21
+ * Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
22
+ * The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
23
+ * or rejected if it fails (e.g., Redis operation fails).
24
+ */
25
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
21
26
  const segmentKey = this.keys.buildSegmentNameKey(name);
22
27
 
23
- if (segmentKeys.length) {
24
- return this.redis.sadd(segmentKey, segmentKeys).then(() => true);
25
- } else {
26
- return Promise.resolve(true);
27
- }
28
- }
29
-
30
- removeFromSegment(name: string, segmentKeys: string[]) {
31
- const segmentKey = this.keys.buildSegmentNameKey(name);
32
-
33
- if (segmentKeys.length) {
34
- return this.redis.srem(segmentKey, segmentKeys).then(() => true);
35
- } else {
36
- return Promise.resolve(true);
37
- }
28
+ return Promise.all([
29
+ addedKeys.length && this.redis.sadd(segmentKey, addedKeys),
30
+ removedKeys.length && this.redis.srem(segmentKey, removedKeys),
31
+ this.redis.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
32
+ ]).then(() => {
33
+ return addedKeys.length > 0 || removedKeys.length > 0;
34
+ });
38
35
  }
39
36
 
40
37
  isInSegment(name: string, key: string) {
@@ -43,12 +40,6 @@ export class SegmentsCacheInRedis implements ISegmentsCacheAsync {
43
40
  ).then(matches => matches !== 0);
44
41
  }
45
42
 
46
- setChangeNumber(name: string, changeNumber: number) {
47
- return this.redis.set(
48
- this.keys.buildSegmentTillKey(name), changeNumber + ''
49
- ).then(status => status === 'OK');
50
- }
51
-
52
43
  getChangeNumber(name: string) {
53
44
  return this.redis.get(this.keys.buildSegmentTillKey(name)).then((value: string | null) => {
54
45
  const i = parseInt(value as string, 10);
@@ -23,33 +23,20 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
23
23
  }
24
24
 
25
25
  /**
26
- * Add a list of `segmentKeys` to the given segment `name`.
27
- * The returned promise is resolved when the operation success
28
- * or rejected if wrapper operation fails.
29
- */
30
- addToSegment(name: string, segmentKeys: string[]) {
31
- const segmentKey = this.keys.buildSegmentNameKey(name);
32
-
33
- if (segmentKeys.length) {
34
- return this.wrapper.addItems(segmentKey, segmentKeys);
35
- } else {
36
- return Promise.resolve();
37
- }
38
- }
39
-
40
- /**
41
- * Remove a list of `segmentKeys` from the given segment `name`.
42
- * The returned promise is resolved when the operation success
43
- * or rejected if wrapper operation fails.
26
+ * Update the given segment `name` with the lists of `addedKeys`, `removedKeys` and `changeNumber`.
27
+ * The returned promise is resolved if the operation success, with `true` if the segment was updated (i.e., some key was added or removed),
28
+ * or rejected if it fails (e.g., wrapper operation fails).
44
29
  */
45
- removeFromSegment(name: string, segmentKeys: string[]) {
30
+ update(name: string, addedKeys: string[], removedKeys: string[], changeNumber: number) {
46
31
  const segmentKey = this.keys.buildSegmentNameKey(name);
47
32
 
48
- if (segmentKeys.length) {
49
- return this.wrapper.removeItems(segmentKey, segmentKeys);
50
- } else {
51
- return Promise.resolve();
52
- }
33
+ return Promise.all<any>([
34
+ addedKeys.length && this.wrapper.addItems(segmentKey, addedKeys),
35
+ removedKeys.length && this.wrapper.removeItems(segmentKey, removedKeys),
36
+ this.wrapper.set(this.keys.buildSegmentTillKey(name), changeNumber + '')
37
+ ]).then(() => {
38
+ return addedKeys.length > 0 || removedKeys.length > 0;
39
+ });
53
40
  }
54
41
 
55
42
  /**
@@ -60,17 +47,6 @@ export class SegmentsCachePluggable implements ISegmentsCacheAsync {
60
47
  return this.wrapper.itemContains(this.keys.buildSegmentNameKey(name), key);
61
48
  }
62
49
 
63
- /**
64
- * Set till number for the given segment `name`.
65
- * The returned promise is resolved when the operation success,
66
- * or rejected if it fails (e.g., wrapper operation fails).
67
- */
68
- setChangeNumber(name: string, changeNumber: number) {
69
- return this.wrapper.set(
70
- this.keys.buildSegmentTillKey(name), changeNumber + ''
71
- );
72
- }
73
-
74
50
  /**
75
51
  * Get till number or -1 if it's not defined.
76
52
  * The returned promise is resolved with the changeNumber or -1 if it doesn't exist or a wrapper operation fails.