@splitsoftware/splitio-commons 3.0.0-rc.0 → 3.0.0-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 (180) hide show
  1. package/CHANGES.txt +8 -3
  2. package/cjs/evaluator/index.js +11 -11
  3. package/cjs/logger/constants.js +4 -4
  4. package/cjs/logger/messages/warn.js +0 -1
  5. package/cjs/readiness/constants.js +4 -4
  6. package/cjs/readiness/readinessManager.js +22 -22
  7. package/cjs/readiness/sdkReadinessManager.js +0 -16
  8. package/cjs/sdkClient/sdkLifecycle.js +2 -1
  9. package/cjs/sdkClient/trackMethod.js +2 -2
  10. package/cjs/sdkFactory/index.js +5 -5
  11. package/cjs/sdkManager/index.js +2 -2
  12. package/cjs/services/authProvider.js +67 -0
  13. package/cjs/services/secureSplitHttpClient.js +39 -0
  14. package/cjs/services/splitApi.js +7 -2
  15. package/cjs/storages/AbstractDefinitionsCacheAsync.js +51 -0
  16. package/cjs/storages/AbstractDefinitionsCacheSync.js +76 -0
  17. package/cjs/storages/KeyBuilder.js +6 -6
  18. package/cjs/storages/KeyBuilderCS.js +5 -5
  19. package/cjs/storages/KeyBuilderSS.js +2 -2
  20. package/cjs/storages/getRolloutPlan.js +3 -3
  21. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +194 -0
  22. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +9 -9
  23. package/cjs/storages/inLocalStorage/index.js +6 -6
  24. package/cjs/storages/inLocalStorage/validateCache.js +3 -3
  25. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +121 -0
  26. package/cjs/storages/inMemory/InMemoryStorage.js +4 -4
  27. package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -5
  28. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -4
  29. package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
  30. package/cjs/storages/inMemory/TelemetryCacheInMemory.js +3 -3
  31. package/cjs/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +77 -77
  32. package/cjs/storages/inRedis/index.js +2 -2
  33. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +212 -0
  34. package/cjs/storages/pluggable/index.js +2 -2
  35. package/cjs/storages/setRolloutPlan.js +5 -5
  36. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +5 -5
  37. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  38. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  39. package/cjs/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
  40. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -8
  41. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  42. package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +1 -1
  43. package/cjs/sync/streaming/SSEHandler/index.js +1 -1
  44. package/cjs/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +15 -15
  45. package/cjs/sync/streaming/constants.js +3 -3
  46. package/cjs/sync/streaming/pushManager.js +13 -13
  47. package/cjs/sync/submitters/telemetrySubmitter.js +1 -2
  48. package/cjs/sync/syncManagerOnline.js +4 -4
  49. package/cjs/utils/Backoff.js +20 -0
  50. package/cjs/utils/lang/sets.js +4 -4
  51. package/cjs/utils/settingsValidation/url.js +1 -1
  52. package/esm/evaluator/index.js +11 -11
  53. package/esm/logger/constants.js +1 -1
  54. package/esm/logger/messages/warn.js +0 -1
  55. package/esm/readiness/constants.js +3 -3
  56. package/esm/readiness/readinessManager.js +23 -23
  57. package/esm/readiness/sdkReadinessManager.js +1 -17
  58. package/esm/sdkClient/sdkLifecycle.js +2 -1
  59. package/esm/sdkClient/trackMethod.js +2 -2
  60. package/esm/sdkFactory/index.js +6 -6
  61. package/esm/sdkManager/index.js +2 -2
  62. package/esm/services/authProvider.js +63 -0
  63. package/esm/services/secureSplitHttpClient.js +35 -0
  64. package/esm/services/splitApi.js +7 -2
  65. package/esm/storages/AbstractDefinitionsCacheAsync.js +48 -0
  66. package/esm/storages/AbstractDefinitionsCacheSync.js +71 -0
  67. package/esm/storages/KeyBuilder.js +6 -6
  68. package/esm/storages/KeyBuilderCS.js +5 -5
  69. package/esm/storages/KeyBuilderSS.js +2 -2
  70. package/esm/storages/getRolloutPlan.js +3 -3
  71. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +191 -0
  72. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -6
  73. package/esm/storages/inLocalStorage/index.js +6 -6
  74. package/esm/storages/inLocalStorage/validateCache.js +3 -3
  75. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +118 -0
  76. package/esm/storages/inMemory/InMemoryStorage.js +4 -4
  77. package/esm/storages/inMemory/InMemoryStorageCS.js +5 -5
  78. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +1 -1
  79. package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
  80. package/esm/storages/inMemory/TelemetryCacheInMemory.js +3 -3
  81. package/esm/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +76 -76
  82. package/esm/storages/inRedis/index.js +2 -2
  83. package/esm/storages/pluggable/DefinitionsCachePluggable.js +209 -0
  84. package/esm/storages/pluggable/index.js +2 -2
  85. package/esm/storages/setRolloutPlan.js +5 -5
  86. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +6 -6
  87. package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  88. package/esm/sync/polling/pollingManagerCS.js +4 -4
  89. package/esm/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
  90. package/esm/sync/polling/updaters/definitionChangesUpdater.js +9 -9
  91. package/esm/sync/polling/updaters/mySegmentsUpdater.js +1 -1
  92. package/esm/sync/streaming/SSEHandler/NotificationKeeper.js +2 -2
  93. package/esm/sync/streaming/SSEHandler/index.js +2 -2
  94. package/esm/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +14 -14
  95. package/esm/sync/streaming/constants.js +2 -2
  96. package/esm/sync/streaming/pushManager.js +14 -14
  97. package/esm/sync/submitters/telemetrySubmitter.js +1 -2
  98. package/esm/sync/syncManagerOnline.js +3 -3
  99. package/esm/utils/Backoff.js +20 -0
  100. package/esm/utils/lang/sets.js +4 -4
  101. package/esm/utils/settingsValidation/url.js +1 -1
  102. package/package.json +1 -1
  103. package/src/dtos/types.ts +2 -2
  104. package/src/evaluator/index.ts +11 -11
  105. package/src/logger/constants.ts +1 -1
  106. package/src/logger/messages/warn.ts +0 -1
  107. package/src/readiness/constants.ts +3 -3
  108. package/src/readiness/readinessManager.ts +22 -22
  109. package/src/readiness/sdkReadinessManager.ts +1 -16
  110. package/src/readiness/types.ts +10 -16
  111. package/src/sdkClient/sdkLifecycle.ts +2 -1
  112. package/src/sdkClient/trackMethod.ts +2 -2
  113. package/src/sdkFactory/index.ts +5 -5
  114. package/src/sdkManager/index.ts +6 -6
  115. package/src/services/authProvider.ts +86 -0
  116. package/src/services/secureSplitHttpClient.ts +44 -0
  117. package/src/services/splitApi.ts +9 -3
  118. package/src/services/types.ts +4 -0
  119. package/src/storages/AbstractDefinitionsCacheAsync.ts +63 -0
  120. package/src/storages/AbstractDefinitionsCacheSync.ts +95 -0
  121. package/src/storages/KeyBuilder.ts +6 -6
  122. package/src/storages/KeyBuilderCS.ts +6 -6
  123. package/src/storages/KeyBuilderSS.ts +2 -2
  124. package/src/storages/getRolloutPlan.ts +3 -3
  125. package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +234 -0
  126. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +6 -6
  127. package/src/storages/inLocalStorage/index.ts +6 -6
  128. package/src/storages/inLocalStorage/validateCache.ts +4 -4
  129. package/src/storages/inMemory/DefinitionsCacheInMemory.ts +130 -0
  130. package/src/storages/inMemory/InMemoryStorage.ts +4 -4
  131. package/src/storages/inMemory/InMemoryStorageCS.ts +5 -5
  132. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +1 -1
  133. package/src/storages/inMemory/SegmentsCacheInMemory.ts +1 -1
  134. package/src/storages/inMemory/TelemetryCacheInMemory.ts +3 -3
  135. package/src/storages/inRedis/{SplitsCacheInRedis.ts → DefinitionsCacheInRedis.ts} +67 -67
  136. package/src/storages/inRedis/index.ts +2 -2
  137. package/src/storages/pluggable/DefinitionsCachePluggable.ts +230 -0
  138. package/src/storages/pluggable/index.ts +2 -2
  139. package/src/storages/setRolloutPlan.ts +7 -7
  140. package/src/storages/types.ts +19 -19
  141. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +8 -8
  142. package/src/sync/polling/fetchers/splitChangesFetcher.ts +3 -3
  143. package/src/sync/polling/pollingManagerCS.ts +4 -4
  144. package/src/sync/polling/syncTasks/definitionsSyncTask.ts +1 -1
  145. package/src/sync/polling/types.ts +1 -1
  146. package/src/sync/polling/updaters/definitionChangesUpdater.ts +11 -11
  147. package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
  148. package/src/sync/streaming/AuthClient/index.ts +2 -2
  149. package/src/sync/streaming/AuthClient/types.ts +14 -11
  150. package/src/sync/streaming/SSEClient/index.ts +2 -2
  151. package/src/sync/streaming/SSEClient/types.ts +2 -2
  152. package/src/sync/streaming/SSEHandler/NotificationKeeper.ts +2 -2
  153. package/src/sync/streaming/SSEHandler/index.ts +2 -2
  154. package/src/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.ts → DefinitionsUpdateWorker.ts} +16 -16
  155. package/src/sync/streaming/constants.ts +2 -2
  156. package/src/sync/streaming/pushManager.ts +16 -16
  157. package/src/sync/streaming/types.ts +2 -2
  158. package/src/sync/submitters/telemetrySubmitter.ts +1 -2
  159. package/src/sync/syncManagerOnline.ts +3 -3
  160. package/src/utils/Backoff.ts +21 -0
  161. package/src/utils/inputValidation/trafficTypeExistence.ts +2 -2
  162. package/src/utils/jwt/types.ts +1 -1
  163. package/src/utils/lang/sets.ts +4 -2
  164. package/src/utils/settingsValidation/url.ts +1 -1
  165. package/types/splitio.d.ts +23 -5
  166. package/cjs/storages/AbstractSplitsCacheAsync.js +0 -51
  167. package/cjs/storages/AbstractSplitsCacheSync.js +0 -76
  168. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -194
  169. package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -121
  170. package/cjs/storages/pluggable/SplitsCachePluggable.js +0 -212
  171. package/esm/storages/AbstractSplitsCacheAsync.js +0 -48
  172. package/esm/storages/AbstractSplitsCacheSync.js +0 -71
  173. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -191
  174. package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -118
  175. package/esm/storages/pluggable/SplitsCachePluggable.js +0 -209
  176. package/src/storages/AbstractSplitsCacheAsync.ts +0 -63
  177. package/src/storages/AbstractSplitsCacheSync.ts +0 -95
  178. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -234
  179. package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -130
  180. package/src/storages/pluggable/SplitsCachePluggable.ts +0 -230
@@ -0,0 +1,95 @@
1
+ import { IDefinitionsCacheSync, IStorageSync } from './types';
2
+ import { IRBSegment, IDefinition } from '../dtos/types';
3
+ import { objectAssign } from '../utils/lang/objectAssign';
4
+ import { IN_SEGMENT, IN_LARGE_SEGMENT } from '../utils/constants';
5
+
6
+ /**
7
+ * This class provides a skeletal implementation of the IDefinitionsCacheSync interface
8
+ * to minimize the effort required to implement this interface.
9
+ */
10
+ export abstract class AbstractDefinitionsCacheSync implements IDefinitionsCacheSync {
11
+
12
+ protected abstract add(definition: IDefinition): boolean
13
+ protected abstract remove(name: string): boolean
14
+ protected abstract setChangeNumber(changeNumber: number): boolean | void
15
+
16
+ update(toAdd: IDefinition[], toRemove: string[], changeNumber: number): boolean {
17
+ let updated = toAdd.map(addedFF => this.add(addedFF)).some(result => result);
18
+ updated = toRemove.map(removedFF => this.remove(removedFF)).some(result => result) || updated;
19
+ this.setChangeNumber(changeNumber);
20
+ return updated;
21
+ }
22
+
23
+ abstract get(name: string): IDefinition | null
24
+
25
+ getMany(names: string[]): Record<string, IDefinition | null> {
26
+ const definitions: Record<string, IDefinition | null> = {};
27
+ names.forEach(name => {
28
+ definitions[name] = this.get(name);
29
+ });
30
+ return definitions;
31
+ }
32
+
33
+ abstract getChangeNumber(): number
34
+
35
+ getAll(): IDefinition[] {
36
+ return this.getNames().map(key => this.get(key) as IDefinition);
37
+ }
38
+
39
+ abstract getNames(): string[]
40
+
41
+ abstract trafficTypeExists(trafficType: string): boolean
42
+
43
+ abstract usesSegments(): boolean
44
+
45
+ abstract clear(): void
46
+
47
+ /**
48
+ * Kill `name` definition and set `defaultTreatment` and `changeNumber`.
49
+ * Used for SPLIT_KILL push notifications.
50
+ *
51
+ * @returns `true` if the operation successed updating the definition, or `false` if no definition is updated,
52
+ * for instance, if the `changeNumber` is old, or if the definition is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
53
+ */
54
+ killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean {
55
+ const definition = this.get(name);
56
+
57
+ if (definition && (!definition.changeNumber || definition.changeNumber < changeNumber)) {
58
+ const newDefinition = objectAssign({}, definition);
59
+ newDefinition.killed = true;
60
+ newDefinition.defaultTreatment = defaultTreatment;
61
+ newDefinition.changeNumber = changeNumber;
62
+
63
+ return this.add(newDefinition);
64
+ }
65
+ return false;
66
+ }
67
+
68
+ abstract getNamesBySets(sets: string[]): Set<string>[]
69
+
70
+ }
71
+
72
+ /**
73
+ * Given a parsed definition, it returns a boolean flagging if its conditions use segments matchers (rules & whitelists).
74
+ * This util is intended to simplify the implementation of `definitionsCache::usesSegments` method
75
+ */
76
+ export function usesSegments(ruleEntity: IDefinition | IRBSegment) {
77
+ const conditions = ruleEntity.conditions || [];
78
+ for (let i = 0; i < conditions.length; i++) {
79
+ const matchers = conditions[i].matcherGroup.matchers;
80
+
81
+ for (let j = 0; j < matchers.length; j++) {
82
+ const matcher = matchers[j].matcherType;
83
+ if (matcher === IN_SEGMENT || matcher === IN_LARGE_SEGMENT) return true;
84
+ }
85
+ }
86
+
87
+ const excluded = (ruleEntity as IRBSegment).excluded;
88
+ if (excluded && excluded.segments && excluded.segments.length > 0) return true;
89
+
90
+ return false;
91
+ }
92
+
93
+ export function usesSegmentsSync(storage: Pick<IStorageSync, 'definitions' | 'rbSegments'>) {
94
+ return storage.definitions.usesSegments() || storage.rbSegments.usesSegments();
95
+ }
@@ -21,19 +21,19 @@ export class KeyBuilder {
21
21
  return `${this.prefix}.trafficType.${trafficType}`;
22
22
  }
23
23
 
24
- buildFlagSetKey(flagSet: string) {
25
- return `${this.prefix}.flagSet.${flagSet}`;
24
+ buildSetKey(set: string) {
25
+ return `${this.prefix}.flagSet.${set}`;
26
26
  }
27
27
 
28
- buildSplitKey(splitName: string) {
29
- return `${this.prefix}.split.${splitName}`;
28
+ buildDefinitionKey(definitionName: string) {
29
+ return `${this.prefix}.split.${definitionName}`;
30
30
  }
31
31
 
32
- buildSplitsTillKey() {
32
+ buildDefinitionsTillKey() {
33
33
  return `${this.prefix}.splits.till`;
34
34
  }
35
35
 
36
- buildSplitKeyPrefix() {
36
+ buildDefinitionKeyPrefix() {
37
37
  return `${this.prefix}.split.`;
38
38
  }
39
39
 
@@ -9,13 +9,13 @@ export interface MySegmentsKeyBuilder {
9
9
 
10
10
  export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
11
11
 
12
- protected readonly regexSplitsCacheKey: RegExp;
12
+ protected readonly regexDefinitionsCacheKey: RegExp;
13
13
  protected readonly matchingKey: string;
14
14
 
15
15
  constructor(prefix: string, matchingKey: string) {
16
16
  super(prefix);
17
17
  this.matchingKey = matchingKey;
18
- this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.(splits?|trafficType|flagSet)\\.`);
18
+ this.regexDefinitionsCacheKey = new RegExp(`^${prefix}\\.(splits?|trafficType|flagSet)\\.`);
19
19
  }
20
20
 
21
21
  /**
@@ -35,15 +35,15 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
35
35
  return `${this.prefix}.splits.lastUpdated`;
36
36
  }
37
37
 
38
- isSplitsCacheKey(key: string) {
39
- return this.regexSplitsCacheKey.test(key);
38
+ isDefinitionsCacheKey(key: string) {
39
+ return this.regexDefinitionsCacheKey.test(key);
40
40
  }
41
41
 
42
42
  buildTillKey() {
43
43
  return `${this.prefix}.${this.matchingKey}.segments.till`;
44
44
  }
45
45
 
46
- isSplitKey(key: string) {
46
+ isDefinitionKey(key: string) {
47
47
  return startsWith(key, `${this.prefix}.split.`);
48
48
  }
49
49
 
@@ -51,7 +51,7 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
51
51
  return startsWith(key, `${this.prefix}.rbsegment.`);
52
52
  }
53
53
 
54
- buildSplitsWithSegmentCountKey() {
54
+ buildDefinitionsWithSegmentCountKey() {
55
55
  return `${this.prefix}.splits.usingSegments`;
56
56
  }
57
57
 
@@ -49,8 +49,8 @@ export class KeyBuilderSS extends KeyBuilder {
49
49
  return `${this.prefix}.events`;
50
50
  }
51
51
 
52
- searchPatternForSplitKeys() {
53
- return `${this.buildSplitKeyPrefix()}*`;
52
+ searchPatternForDefinitionKeys() {
53
+ return `${this.buildDefinitionKeyPrefix()}*`;
54
54
  }
55
55
 
56
56
  searchPatternForRBSegmentKeys() {
@@ -12,16 +12,16 @@ import { IMembershipsResponse, IMySegmentsResponse } from '../dtos/types';
12
12
  export function getRolloutPlan(log: ILogger, storage: IStorageSync, options: SplitIO.RolloutPlanOptions = {}): RolloutPlan {
13
13
 
14
14
  const { keys, exposeSegments } = options;
15
- const { splits, segments, rbSegments } = storage;
15
+ const { definitions, segments, rbSegments } = storage;
16
16
 
17
17
  log.debug(`storage: get feature flags${keys ? `, and memberships for keys: ${keys}` : ''}${exposeSegments ? ', and segments' : ''}`);
18
18
 
19
19
  return {
20
20
  splitChanges: {
21
21
  ff: {
22
- t: splits.getChangeNumber(),
22
+ t: definitions.getChangeNumber(),
23
23
  s: -1,
24
- d: splits.getAll(),
24
+ d: definitions.getAll(),
25
25
  },
26
26
  rbs: {
27
27
  t: rbSegments.getChangeNumber(),
@@ -0,0 +1,234 @@
1
+ import { IDefinition } from '../../dtos/types';
2
+ import { AbstractDefinitionsCacheSync, usesSegments } from '../AbstractDefinitionsCacheSync';
3
+ import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
4
+ import { KeyBuilderCS } from '../KeyBuilderCS';
5
+ import { ILogger } from '../../logger/types';
6
+ import { LOG_PREFIX } from './constants';
7
+ import { ISettings } from '../../types';
8
+ import { setToArray } from '../../utils/lang/sets';
9
+ import { StorageAdapter } from '../types';
10
+
11
+ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
12
+
13
+ private readonly keys: KeyBuilderCS;
14
+ private readonly log: ILogger;
15
+ private readonly setsFilter: string[];
16
+ private hasSync?: boolean;
17
+ private readonly storage: StorageAdapter;
18
+
19
+ constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
20
+ super();
21
+ this.keys = keys;
22
+ this.log = settings.log;
23
+ this.setsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
24
+ this.storage = storage;
25
+ }
26
+
27
+ private _decrementCount(key: string) {
28
+ const count = toNumber(this.storage.getItem(key)) - 1;
29
+ if (count > 0) this.storage.setItem(key, count + '');
30
+ else this.storage.removeItem(key);
31
+ }
32
+
33
+ private _decrementCounts(definition: IDefinition) {
34
+ try {
35
+ const ttKey = this.keys.buildTrafficTypeKey(definition.trafficTypeName);
36
+ this._decrementCount(ttKey);
37
+
38
+ if (usesSegments(definition)) {
39
+ const segmentsCountKey = this.keys.buildDefinitionsWithSegmentCountKey();
40
+ this._decrementCount(segmentsCountKey);
41
+ }
42
+ } catch (e) {
43
+ this.log.error(LOG_PREFIX + e);
44
+ }
45
+ }
46
+
47
+ private _incrementCounts(definition: IDefinition) {
48
+ try {
49
+ const ttKey = this.keys.buildTrafficTypeKey(definition.trafficTypeName);
50
+ this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
51
+
52
+ if (usesSegments(definition)) {
53
+ const segmentsCountKey = this.keys.buildDefinitionsWithSegmentCountKey();
54
+ this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
55
+ }
56
+ } catch (e) {
57
+ this.log.error(LOG_PREFIX + e);
58
+ }
59
+ }
60
+
61
+
62
+ /**
63
+ * Removes all definitions related data from localStorage (splits, counters, changeNumber and lastUpdated).
64
+ * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
65
+ */
66
+ clear() {
67
+ // collect item keys
68
+ const len = this.storage.length;
69
+ const accum = [];
70
+ for (let cur = 0; cur < len; cur++) {
71
+ const key = this.storage.key(cur);
72
+ if (key != null && this.keys.isDefinitionsCacheKey(key)) accum.push(key);
73
+ }
74
+ // remove items
75
+ accum.forEach(key => {
76
+ this.storage.removeItem(key);
77
+ });
78
+
79
+ this.hasSync = false;
80
+ }
81
+
82
+ add(definition: IDefinition) {
83
+ const name = definition.name;
84
+ const definitionKey = this.keys.buildDefinitionKey(name);
85
+ const definitionFromStorage = this.storage.getItem(definitionKey);
86
+ const previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
87
+
88
+ if (previousDefinition) {
89
+ this._decrementCounts(previousDefinition);
90
+ this.removeFromSets(previousDefinition.name, previousDefinition.sets);
91
+ }
92
+
93
+ this.storage.setItem(definitionKey, JSON.stringify(definition));
94
+
95
+ this._incrementCounts(definition);
96
+ this.addToSets(definition);
97
+
98
+ return true;
99
+ }
100
+
101
+ remove(name: string): boolean {
102
+ const definition = this.get(name);
103
+ if (!definition) return false;
104
+
105
+ this.storage.removeItem(this.keys.buildDefinitionKey(name));
106
+
107
+ this._decrementCounts(definition);
108
+ this.removeFromSets(definition.name, definition.sets);
109
+
110
+ return true;
111
+ }
112
+
113
+ get(name: string): IDefinition | null {
114
+ const item = this.storage.getItem(this.keys.buildDefinitionKey(name));
115
+ return item && JSON.parse(item);
116
+ }
117
+
118
+ setChangeNumber(changeNumber: number): boolean {
119
+ try {
120
+ this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
121
+ // update "last updated" timestamp with current time
122
+ this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
123
+ this.hasSync = true;
124
+ return true;
125
+ } catch (e) {
126
+ this.log.error(LOG_PREFIX + e);
127
+ return false;
128
+ }
129
+ }
130
+
131
+ getChangeNumber(): number {
132
+ const n = -1;
133
+ let value: string | number | null = this.storage.getItem(this.keys.buildDefinitionsTillKey());
134
+
135
+ if (value !== null) {
136
+ value = parseInt(value, 10);
137
+
138
+ return isNaNNumber(value) ? n : value;
139
+ }
140
+
141
+ return n;
142
+ }
143
+
144
+ getNames(): string[] {
145
+ const len = this.storage.length;
146
+ const accum = [];
147
+
148
+ let cur = 0;
149
+
150
+ while (cur < len) {
151
+ const key = this.storage.key(cur);
152
+
153
+ if (key != null && this.keys.isDefinitionKey(key)) accum.push(this.keys.extractKey(key));
154
+
155
+ cur++;
156
+ }
157
+
158
+ return accum;
159
+ }
160
+
161
+ trafficTypeExists(trafficType: string): boolean {
162
+ const ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
163
+ return isFiniteNumber(ttCount) && ttCount > 0;
164
+ }
165
+
166
+ usesSegments() {
167
+ // If cache hasn't been synchronized with the cloud, assume we need them.
168
+ if (!this.hasSync) return true;
169
+
170
+ const storedCount = this.storage.getItem(this.keys.buildDefinitionsWithSegmentCountKey());
171
+ const definitionsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
172
+
173
+ return isFiniteNumber(definitionsWithSegmentsCount) ?
174
+ definitionsWithSegmentsCount > 0 :
175
+ true;
176
+ }
177
+
178
+ getNamesBySets(sets: string[]): Set<string>[] {
179
+ return sets.map(set => {
180
+ const setKey = this.keys.buildSetKey(set);
181
+ const setFromStorage = this.storage.getItem(setKey);
182
+
183
+ return new Set(setFromStorage ? JSON.parse(setFromStorage) : []);
184
+ });
185
+ }
186
+
187
+ private addToSets(definition: IDefinition) {
188
+ if (!definition.sets) return;
189
+
190
+ definition.sets.forEach(set => {
191
+
192
+ if (this.setsFilter.length > 0 && !this.setsFilter.some(filterSet => filterSet === set)) return;
193
+
194
+ const setKey = this.keys.buildSetKey(set);
195
+
196
+ const setFromStorage = this.storage.getItem(setKey);
197
+
198
+ const setCache = new Set(setFromStorage ? JSON.parse(setFromStorage) : []);
199
+
200
+ if (setCache.has(definition.name)) return;
201
+
202
+ setCache.add(definition.name);
203
+
204
+ this.storage.setItem(setKey, JSON.stringify(setToArray(setCache)));
205
+ });
206
+ }
207
+
208
+ private removeFromSets(definitionName: string, sets?: string[] | null) {
209
+ if (!sets) return;
210
+
211
+ sets.forEach(set => {
212
+ this.removeNames(set, definitionName);
213
+ });
214
+ }
215
+
216
+ private removeNames(setName: string, definitionName: string) {
217
+ const setKey = this.keys.buildSetKey(setName);
218
+
219
+ const setFromStorage = this.storage.getItem(setKey);
220
+
221
+ if (!setFromStorage) return;
222
+
223
+ const setCache = new Set(JSON.parse(setFromStorage));
224
+ setCache.delete(definitionName);
225
+
226
+ if (setCache.size === 0) {
227
+ this.storage.removeItem(setKey);
228
+ return;
229
+ }
230
+
231
+ this.storage.setItem(setKey, JSON.stringify(setToArray(setCache)));
232
+ }
233
+
234
+ }
@@ -3,7 +3,7 @@ import { ILogger } from '../../logger/types';
3
3
  import { ISettings } from '../../types';
4
4
  import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
5
5
  import { setToArray } from '../../utils/lang/sets';
6
- import { usesSegments } from '../AbstractSplitsCacheSync';
6
+ import { usesSegments } from '../AbstractDefinitionsCacheSync';
7
7
  import { KeyBuilderCS } from '../KeyBuilderCS';
8
8
  import { IRBSegmentsCacheSync, StorageAdapter } from '../types';
9
9
  import { LOG_PREFIX } from './constants';
@@ -42,7 +42,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
42
42
  }
43
43
 
44
44
  private updateSegmentCount(diff: number) {
45
- const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
45
+ const segmentsCountKey = this.keys.buildDefinitionsWithSegmentCountKey();
46
46
  const count = toNumber(this.storage.getItem(segmentsCountKey)) + diff;
47
47
  if (count > 0) this.storage.setItem(segmentsCountKey, count + '');
48
48
  else this.storage.removeItem(segmentsCountKey);
@@ -121,11 +121,11 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
121
121
  }
122
122
 
123
123
  usesSegments(): boolean {
124
- const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
125
- const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
124
+ const storedCount = this.storage.getItem(this.keys.buildDefinitionsWithSegmentCountKey());
125
+ const definitionsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
126
126
 
127
- return isFiniteNumber(splitsWithSegmentsCount) ?
128
- splitsWithSegmentsCount > 0 :
127
+ return isFiniteNumber(definitionsWithSegmentsCount) ?
128
+ definitionsWithSegmentsCount > 0 :
129
129
  true;
130
130
  }
131
131
 
@@ -5,7 +5,7 @@ import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory, StorageAdapte
5
5
  import { validatePrefix } from '../KeyBuilder';
6
6
  import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
7
7
  import { isLocalStorageAvailable, isValidStorageWrapper, isWebStorage } from '../../utils/env/isLocalStorageAvailable';
8
- import { SplitsCacheInLocal } from './SplitsCacheInLocal';
8
+ import { DefinitionsCacheInLocal } from './DefinitionsCacheInLocal';
9
9
  import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
10
10
  import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
11
11
  import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
@@ -50,26 +50,26 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
50
50
  const matchingKey = getMatching(settings.core.key);
51
51
  const keys = new KeyBuilderCS(prefix, matchingKey);
52
52
 
53
- const splits = new SplitsCacheInLocal(settings, keys, storage);
53
+ const definitions = new DefinitionsCacheInLocal(settings, keys, storage);
54
54
  const rbSegments = new RBSegmentsCacheInLocal(settings, keys, storage);
55
55
  const segments = new MySegmentsCacheInLocal(log, keys, storage);
56
56
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage);
57
57
  let validateCachePromise: Promise<SplitIO.SdkReadyMetadata> | undefined;
58
58
 
59
59
  return {
60
- splits,
60
+ definitions,
61
61
  rbSegments,
62
62
  segments,
63
63
  largeSegments,
64
64
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
65
65
  impressionCounts: new ImpressionCountsCacheInMemory(),
66
66
  events: new EventsCacheInMemory(eventsQueueSize),
67
- telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
67
+ telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(definitions, segments) : undefined,
68
68
  uniqueKeys: new UniqueKeysCacheInMemoryCS(),
69
69
 
70
70
  validateCache() {
71
71
  if (!validateCachePromise) {
72
- validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments);
72
+ validateCachePromise = validateCache(options, storage, settings, keys, definitions, rbSegments, segments, largeSegments);
73
73
  }
74
74
  return validateCachePromise;
75
75
  },
@@ -86,7 +86,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
86
86
  shared(matchingKey: string) {
87
87
 
88
88
  return {
89
- splits: this.splits,
89
+ definitions: this.definitions,
90
90
  rbSegments: this.rbSegments,
91
91
  segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey), storage),
92
92
  largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage),
@@ -2,7 +2,7 @@ import { ISettings } from '../../types';
2
2
  import { isFiniteNumber, isNaNNumber } from '../../utils/lang';
3
3
  import { getStorageHash } from '../KeyBuilder';
4
4
  import { LOG_PREFIX } from './constants';
5
- import type { SplitsCacheInLocal } from './SplitsCacheInLocal';
5
+ import type { DefinitionsCacheInLocal } from './DefinitionsCacheInLocal';
6
6
  import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
7
7
  import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
8
8
  import { KeyBuilderCS } from '../KeyBuilderCS';
@@ -68,11 +68,11 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
68
68
  *
69
69
  * @returns Metadata object with `initialCacheLoad` (true if is fresh install, false if is ready from cache) and `lastUpdateTimestamp` (timestamp of last cache update or undefined)
70
70
  */
71
- export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise<SplitIO.SdkReadyMetadata> {
71
+ export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, definitions: DefinitionsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise<SplitIO.SdkReadyMetadata> {
72
72
 
73
73
  return Promise.resolve(storage.load && storage.load()).then(() => {
74
74
  const currentTimestamp = Date.now();
75
- const isThereCache = splits.getChangeNumber() > -1;
75
+ const isThereCache = definitions.getChangeNumber() > -1;
76
76
 
77
77
  // Get lastUpdateTimestamp from storage
78
78
  const lastUpdatedTimestampStr = storage.getItem(keys.buildLastUpdatedKey());
@@ -80,7 +80,7 @@ export function validateCache(options: SplitIO.InLocalStorageOptions, storage: S
80
80
  const lastUpdateTimestamp = (!isNaNNumber(lastUpdatedTimestamp) && lastUpdatedTimestamp !== undefined) ? lastUpdatedTimestamp : undefined;
81
81
 
82
82
  if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
83
- splits.clear();
83
+ definitions.clear();
84
84
  rbSegments.clear();
85
85
  segments.clear();
86
86
  largeSegments.clear();
@@ -0,0 +1,130 @@
1
+ import { IDefinition, ISplitFiltersValidation } from '../../dtos/types';
2
+ import { AbstractDefinitionsCacheSync, usesSegments } from '../AbstractDefinitionsCacheSync';
3
+ import { isFiniteNumber } from '../../utils/lang';
4
+
5
+ /**
6
+ * Default IDefinitionsCacheSync implementation that stores split definitions in memory.
7
+ */
8
+ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
9
+
10
+ private setsFilter: string[];
11
+ private definitionsCache: Record<string, IDefinition> = {};
12
+ private ttCache: Record<string, number> = {};
13
+ private changeNumber: number = -1;
14
+ private segmentsCount: number = 0;
15
+ private setsCache: Record<string, Set<string>> = {};
16
+
17
+ constructor(splitFiltersValidation?: ISplitFiltersValidation) {
18
+ super();
19
+ this.setsFilter = splitFiltersValidation ? splitFiltersValidation.groupedFilters.bySet : [];
20
+ }
21
+
22
+ clear() {
23
+ this.definitionsCache = {};
24
+ this.ttCache = {};
25
+ this.changeNumber = -1;
26
+ this.segmentsCount = 0;
27
+ this.setsCache = {};
28
+ }
29
+
30
+ add(definition: IDefinition): boolean {
31
+ const name = definition.name;
32
+ const previousDefinition = this.get(name);
33
+ if (previousDefinition) { // We had this Split already
34
+
35
+ const previousTtName = previousDefinition.trafficTypeName;
36
+ this.ttCache[previousTtName]--;
37
+ if (!this.ttCache[previousTtName]) delete this.ttCache[previousTtName];
38
+
39
+ this.removeFromFlagSets(previousDefinition.name, previousDefinition.sets);
40
+
41
+ // Subtract from segments count for the previous version of this Split
42
+ if (usesSegments(previousDefinition)) this.segmentsCount--;
43
+ }
44
+
45
+ // Store the Split.
46
+ this.definitionsCache[name] = definition;
47
+ // Update TT cache
48
+ const ttName = definition.trafficTypeName;
49
+ this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1;
50
+ this.addToFlagSets(definition);
51
+
52
+ // Add to segments count for the new version of the Split
53
+ if (usesSegments(definition)) this.segmentsCount++;
54
+
55
+ return true;
56
+ }
57
+
58
+ remove(name: string): boolean {
59
+ const definition = this.get(name);
60
+ if (!definition) return false;
61
+
62
+ // Delete the Split
63
+ delete this.definitionsCache[name];
64
+
65
+ const ttName = definition.trafficTypeName;
66
+ this.ttCache[ttName]--; // Update tt cache
67
+ if (!this.ttCache[ttName]) delete this.ttCache[ttName];
68
+ this.removeFromFlagSets(definition.name, definition.sets);
69
+
70
+ // Update the segments count.
71
+ if (usesSegments(definition)) this.segmentsCount--;
72
+
73
+ return true;
74
+ }
75
+
76
+ get(name: string): IDefinition | null {
77
+ return this.definitionsCache[name] || null;
78
+ }
79
+
80
+ setChangeNumber(changeNumber: number): boolean {
81
+ this.changeNumber = changeNumber;
82
+ return true;
83
+ }
84
+
85
+ getChangeNumber(): number {
86
+ return this.changeNumber;
87
+ }
88
+
89
+ getNames(): string[] {
90
+ return Object.keys(this.definitionsCache);
91
+ }
92
+
93
+ trafficTypeExists(trafficType: string): boolean {
94
+ return isFiniteNumber(this.ttCache[trafficType]) && this.ttCache[trafficType] > 0;
95
+ }
96
+
97
+ usesSegments(): boolean {
98
+ return this.getChangeNumber() === -1 || this.segmentsCount > 0;
99
+ }
100
+
101
+ getNamesBySets(sets: string[]): Set<string>[] {
102
+ return sets.map(set => this.setsCache[set] || new Set());
103
+ }
104
+
105
+ private addToFlagSets(featureFlag: IDefinition) {
106
+ if (!featureFlag.sets) return;
107
+ featureFlag.sets.forEach(featureFlagSet => {
108
+
109
+ if (this.setsFilter.length > 0 && !this.setsFilter.some(filterFlagSet => filterFlagSet === featureFlagSet)) return;
110
+
111
+ if (!this.setsCache[featureFlagSet]) this.setsCache[featureFlagSet] = new Set([]);
112
+
113
+ this.setsCache[featureFlagSet].add(featureFlag.name);
114
+ });
115
+ }
116
+
117
+ private removeFromFlagSets(featureFlagName: string, sets?: string[] | null) {
118
+ if (!sets) return;
119
+ sets.forEach(set => {
120
+ this.removeNames(set, featureFlagName);
121
+ });
122
+ }
123
+
124
+ private removeNames(setName: string, featureFlagName: string) {
125
+ if (!this.setsCache[setName]) return;
126
+ this.setsCache[setName].delete(featureFlagName);
127
+ if (this.setsCache[setName].size === 0) delete this.setsCache[setName];
128
+ }
129
+
130
+ }